diff --git a/bin/svndbadmin b/bin/svndbadmin index b602b368..f698bcda 100755 --- a/bin/svndbadmin +++ b/bin/svndbadmin @@ -56,25 +56,86 @@ else: ######################################################################### -import os import re +import tempfile -import svn.core -import svn.repos -import svn.fs -import svn.delta +import vclib.altsvn._svn as _svn +import vclib.altsvn._svn_repos as _repos +from vclib import _diff_fp import cvsdb import viewvc -import vclib + +# as vclib.altsvn._svn* modules and svn swig python binding modules are +# exclusive we must delay import them until loading config. +# here is a flag and placeholders +svn_module = None +_core = None +_repos = None +_fs = None +_delta = None +_v = None +_v_repos = None + + +def import_svn_module(cfg): + global svn_module + global _v, _v_repos + global _core, _repos, _fs, _delta + if svn_module: + return + if getattr(cfg, 'vclib') and getattr(cfg.vclib, 'use_altsvn', None): + try: + import vclib.altsvn._svn as _v + import vclib.altsvn._svn_repos as _v_repos + svn_module = 'altsvn' + except ImportError: + import svn.core as _core + import svn.repos as _repos + import svn.fs as _fs + import svn.delta as _delta + svn_module = 'svn' + else: + import svn.core as _core + import svn.repos as _repos + import svn.fs as _fs + import svn.delta as _delta + svn_module = 'svn' + # expose constants + global SVN_PROP_REVISION_AUTHOR, SVN_PROP_REVISION_DATE, \ + SVN_PROP_REVISION_LOG, svn_node_file, CHANGE_ACTION_DELETE + if svn_module == 'svn': + SVN_PROP_REVISION_AUTHOR = _core.SVN_PROP_REVISION_AUTHOR + SVN_PROP_REVISION_DATE = _core.SVN_PROP_REVISION_DATE + SVN_PROP_REVISION_LOG = _core.SVN_PROP_REVISION_LOG + svn_node_file = _core.svn_node_file + CHANGE_ACTION_DELETE = _repos.CHANGE_ACTION_DELETE + else: + SVN_PROP_REVISION_AUTHOR = _v.SVN_PROP_REVISION_AUTHOR + SVN_PROP_REVISION_DATE = _v.SVN_PROP_REVISION_DATE + SVN_PROP_REVISION_LOG = _v.SVN_PROP_REVISION_LOG + svn_node_file = _v.svn_node_file + CHANGE_ACTION_DELETE = _v_repos.svn_fs_path_change_delete class SvnRepo: """Class used to manage a connection to a SVN repository.""" def __init__(self, path): self.path = path - self.repo = svn.repos.svn_repos_open(path) - self.fs = svn.repos.svn_repos_fs(self.repo) - self.rev_max = svn.fs.youngest_rev(self.fs) + if svn_module == "svn": + self.repo = _repos.svn_repos_open(path) + self.fs = _repos.svn_repos_fs(self.repo) + self.rev_max = _fs.youngest_rev(self.fs) + else: + self.result_pool = _v.Apr_Pool() + self.scratch_pool = _v.Apr_Pool() + self.repo = _v_repos.svn_repos_open( + path, + self.result_pool, + self.scratch_pool) + self.fs = _v_repos.svn_repos_fs(self.repo) + self.rev_max = _v_repos.svn_fs_youngest_rev( + self.fs, self.scratch_pool) + self.scratch_pool.clear() def __getitem__(self, rev): if rev is None: rev = self.rev_max @@ -84,6 +145,38 @@ class SvnRepo: rev = SvnRev(self, rev) return rev +# alternative of svn.fs.FileDiff class. used if svn_module is 'altsvn' +class Diff_fp(_diff_fp): + def __init__(self, root1, path1, root2, path2, pool=None, diffoptions=[]): + def get_contents(root, path, scratch_pool): + fd, temp = tempfile.mkstemp() + if root and path: + fp = os.fdopen(fd, "wb") + try: + stream = _v_repos.svn_fs_file_contents( + root, path, scratch_pool) + try: + while 1: + chunk, len = _v.svn_stream_read_full( + stream, _v.SVN_STREAM_CHUNK_SIZE) + if not chunk: + break + fp.write(chunk) + finally: + _v.svn_stream_close(stream) + finally: + fp.close() + else: + os.close(fd) + return temp + + assert path1 or path2 + temp1 = get_contents(root1, path1, pool) + temp2 = get_contents(root2, path2, pool) + _diff_fp.__init__(self, temp1, temp2, info1=None, info2=None, + diff_cmd='diff', diff_opts=diffoptions) + + _re_diff_change_command = re.compile('(\d+)(?:,(\d+))?([acd])(\d+)(?:,(\d+))?') def _get_diff_counts(diff_fp): @@ -134,32 +227,47 @@ class SvnRev: def __init__(self, repo, rev): self.repo = repo self.rev = rev - self.rev_roots = {} # cache of revision roots + if svn_module == "svn": + self.rev_roots = {} # cache of revision roots + else: + self.scratch_pool = repo.scratch_pool # revision properties ... - revprops = svn.fs.revision_proplist(repo.fs, rev) - self.author = str(revprops.get(svn.core.SVN_PROP_REVISION_AUTHOR,'')) - self.date = str(revprops.get(svn.core.SVN_PROP_REVISION_DATE, '')) - self.log = str(revprops.get(svn.core.SVN_PROP_REVISION_LOG, '')) + if svn_module == "svn": + revprops = _fs.svn_fs_revision_proplist(repo.fs, rev) + else: + revprops = _v_repos.svn_fs_revision_proplist(repo.fs, rev, + self.scratch_pool) + self.author = str(revprops.get(SVN_PROP_REVISION_AUTHOR,'')) + self.date = str(revprops.get(SVN_PROP_REVISION_DATE, '')) + self.log = str(revprops.get(SVN_PROP_REVISION_LOG, '')) # convert the date string to seconds since epoch ... - try: - self.date = svn.core.svn_time_from_cstring(self.date) / 1000000 - except: - self.date = None + if svn_module == "svn": + try: + self.date = _core.svn_time_from_cstring(self.date) // 1000000 + except: + self.date = None + else: + self.date = _v.datestr_to_date(self.date, self.scratch_pool) # get a root for the current revisions fsroot = self._get_root_for_rev(rev) - + # find changes in the revision - editor = svn.repos.ChangeCollector(repo.fs, fsroot) - e_ptr, e_baton = svn.delta.make_editor(editor) - svn.repos.svn_repos_replay(fsroot, e_ptr, e_baton) + if svn_module == "svn": + editor = _repos.ChangeCollector(repo.fs, fsroot) + e_ptr, e_baton = _delta.make_editor(editor) + _repos.svn_repos_replay(fsroot, e_ptr, e_baton) + changes = editor.changes + else: + changes = _v_repos._get_changed_paths_helper(repo.fs, fsroot, + self.scratch_pool) self.changes = [] - for path, change in editor.changes.items(): + for path, change in changes.items(): # skip non-file changes - if change.item_kind != svn.core.svn_node_file: + if change.item_kind != svn_node_file: continue # deal with the change types we handle @@ -175,7 +283,7 @@ class SvnRev: # because back then deleted paths always had a change.path # of None. if hasattr(change, 'action') \ - and change.action == svn.repos.CHANGE_ACTION_DELETE: + and change.action == CHANGE_ACTION_DELETE: action = 'remove' elif not change.path: action = 'remove' @@ -184,22 +292,33 @@ class SvnRev: else: action = 'change' - if action == 'remove': - diffobj = svn.fs.FileDiff(base_root, base_path, None, None) + if svn_module == "svn": + if action == 'remove': + diffobj = _fs.FileDiff(base_root, base_path, None, None) + else: + diffobj = _fs.FileDiff(base_root, base_path, + fsroot, change.path) + diff_fp = diffobj.get_pipe() else: - diffobj = svn.fs.FileDiff(base_root, base_path, - fsroot, change.path) + if action == 'remove': + diff_fp = Diff_fp(base_root, base_path, None, None, + self.scratch_pool) + else: + diff_fp = Diff_fp(base_root, base_path, + fsroot, change.path, self.scratch_pool) - diff_fp = diffobj.get_pipe() plus, minus = _get_diff_counts(diff_fp) self.changes.append((path, action, plus, minus)) def _get_root_for_rev(self, rev): """Fetch a revision root from a cache of such, or a fresh root (which is then cached for later use.""" - if not self.rev_roots.has_key(rev): - self.rev_roots[rev] = svn.fs.revision_root(self.repo.fs, rev) - return self.rev_roots[rev] + if svn_module == "svn": + if not self.rev_roots.has_key(rev): + self.rev_roots[rev] = _fs.revision_root(self.repo.fs, rev) + return self.rev_roots[rev] + else: + return self.repo.fs._getroot(rev) def handle_revision(db, command, repo, rev, verbose, force=0): @@ -251,8 +370,17 @@ def handle_revision(db, command, repo, rev, verbose, force=0): def main(command, repository, revs=[], verbose=0, force=0): cfg = viewvc.load_config(CONF_PATHNAME) + import_svn_module(cfg) db = cvsdb.ConnectDatabase(cfg) + # sanitise repository path + if svn_module == 'svn': + from vclib.svn import canonicalize_rootpath + repository = canonicalize_rootpath(repository) + else: + repository = _v.canonicalize_rootpath(repository) + repository = cvsdb.CleanRepository(os.path.abspath(repository)) + # Purge what must be purged. if command in ('rebuild', 'purge'): if verbose: @@ -314,7 +442,7 @@ Usage: 1. %s [-v] rebuild REPOS-PATH the database. If a range is specified, the revisions will be processed in ascending order, and you may specify "HEAD" to indicate "the youngest revision currently in the repository". - + 3. Purge information specific to the repository located at REPOS-PATH from the database. @@ -339,7 +467,7 @@ if __name__ == '__main__': del args[index] except ValueError: pass - + if len(args) < 3: usage() @@ -370,9 +498,9 @@ if __name__ == '__main__': rev = None try: - repository = vclib.svn.canonicalize_rootpath(args[2]) - repository = cvsdb.CleanRepository(os.path.abspath(repository)) - main(command, repository, revs, verbose, force) + # we don't clean args[2] as repository path here, because + # subversion access modules are not imported yet. + main(command, args[2], revs, verbose, force) except KeyboardInterrupt: print print '** break **' diff --git a/conf/viewvc.conf.dist b/conf/viewvc.conf.dist index 7e58ceaf..8a84224c 100644 --- a/conf/viewvc.conf.dist +++ b/conf/viewvc.conf.dist @@ -1229,3 +1229,11 @@ #force_username_case = ##--------------------------------------------------------------------------- +[vclib] + +## determine to use alternative subversion access module in vclib +## if it is available, or not (default: 0 (not used)) +#use_altsvn = +use_altsvn = 1 + +##--------------------------------------------------------------------------- diff --git a/lib/config.py b/lib/config.py index 55fddd83..b813e029 100644 --- a/lib/config.py +++ b/lib/config.py @@ -92,6 +92,11 @@ import fnmatch # | query | # | | # `-----------' +# ,-----------. +# | | +# | vclib | +# | | +# `-----------' # # ### TODO: Figure out what this all means for the 'kv' stuff. # @@ -107,6 +112,7 @@ class Config: 'query', 'templates', 'utilities', + 'vclib', ) _force_multi_value = ( # Configuration values with multiple, comma-separated values. @@ -469,6 +475,8 @@ class Config: self.cvsdb.rss_row_limit = 100 self.cvsdb.check_database_for_root = 0 + self.vclib.use_altsvn = 0 + def _startswith(somestr, substr): return somestr[:len(substr)] == substr diff --git a/lib/vclib/__init__.py b/lib/vclib/__init__.py index 85ba63e2..f5ea830c 100644 --- a/lib/vclib/__init__.py +++ b/lib/vclib/__init__.py @@ -350,7 +350,7 @@ def _diff_args(type, options): """generate argument list to pass to diff or rcsdiff""" args = [] if type == CONTEXT: - if options.has_key('context'): + if 'context' in options: if options['context'] is None: args.append('--context=-1') else: @@ -358,7 +358,7 @@ def _diff_args(type, options): else: args.append('-c') elif type == UNIFIED: - if options.has_key('context'): + if 'context' in options: if options['context'] is None: args.append('--unified=-1') else: diff --git a/lib/viewvc.py b/lib/viewvc.py index 5009f4f9..5e97cc20 100644 --- a/lib/viewvc.py +++ b/lib/viewvc.py @@ -48,7 +48,9 @@ import sapi import vcauth import vclib import vclib.ccvs -import vclib.svn +# delay import vclib.svn because it is not determined which is to be imported +# svn and altsvn until get options. this is a placeholder for the module +vclib_svn = None try: import idiff @@ -114,6 +116,17 @@ class Request: # check for an authenticated username self.username = server.getenv('REMOTE_USER') + # check which module to be imported to handle svn repository + global vclib_svn + if vclib_svn is None: + if getattr(cfg, 'vclib') and getattr(cfg.vclib, 'use_altsvn', None): + try: + import vclib.altsvn as vclib_svn + except ImportError: + import vclib.svn as vclib_svn + else: + import vclib.svn as vclib_svn + # if we allow compressed output, see if the client does too self.gzip_compress_level = 0 if cfg.options.allow_compress: @@ -272,8 +285,8 @@ class Request: # $CVSHeader$ os.environ['CVSROOT'] = self.rootpath elif roottype == 'svn': - self.rootpath = vclib.svn.canonicalize_rootpath(rootpath) - self.repos = vclib.svn.SubversionRepository(self.rootname, + self.rootpath = vclib_svn.canonicalize_rootpath(rootpath) + self.repos = vclib_svn.SubversionRepository(self.rootname, self.rootpath, self.auth, cfg.utilities, @@ -4962,7 +4975,7 @@ def list_roots(request): for root in cfg.general.svn_roots.keys(): auth = setup_authorizer(cfg, request.username, root) try: - repos = vclib.svn.SubversionRepository(root, cfg.general.svn_roots[root], + repos = vclib_svn.SubversionRepository(root, cfg.general.svn_roots[root], auth, cfg.utilities, cfg.options.svn_config_dir) lastmod = None @@ -5036,7 +5049,7 @@ def expand_root_parents(cfg): else: cfg.general.cvs_roots.update(roots) elif repo_type == 'svn': - roots = vclib.svn.expand_root_parent(path) + roots = vclib_svn.expand_root_parent(path) if context: fullroots = {} for root, rootpath in roots.iteritems(): @@ -5084,7 +5097,7 @@ def find_root_in_parents(cfg, path_parts, roottype): if roottype == 'cvs': rootpath = vclib.ccvs.find_root_in_parent(path, rootname) elif roottype == 'svn': - rootpath = vclib.svn.find_root_in_parent(path, rootname) + rootpath = vclib_svn.find_root_in_parent(path, rootname) if rootpath is not None: return fullroot, rootpath, remain diff --git a/src/lib/.gitignore b/src/lib/.gitignore new file mode 100644 index 00000000..73febc40 --- /dev/null +++ b/src/lib/.gitignore @@ -0,0 +1,13 @@ +__pycache__ +build +cfg.py +cfg.pyc +cfg.pyo +cython/capi/subversion_1/_svn_api_ver.pxi +vclib/altsvn/_svn.c +vclib/altsvn/_svn_fs.c +vclib/altsvn/_svn_repos.c +vclib/altsvn/_svn_ra.c +vclib/altsvn/_svn_api_ver.pxi +vclib/altsvn/_py_ver.pxi +vclib/altsvn/make_svn_api_version_pxi diff --git a/src/lib/README-altsvn.txt b/src/lib/README-altsvn.txt new file mode 100644 index 00000000..aa8e686b --- /dev/null +++ b/src/lib/README-altsvn.txt @@ -0,0 +1,92 @@ +vclib.altsvn --- alternative module to access Subversion repository for ViewVC + +[Overview] +This is one of replacement of vclib.svn module, to support Python 3.x. +It doesn't use swig Python binding of Subversion API, which is not support +Python 3.x yet, but uses bridge module to access C API, written in Cython. + + +[Build Requirement] +* Python 2.6/2.7 or 3.x (for 3.x, only tested on 3.6 and 3.7) +* Cython 0.28 or above (not tested in 0.27 and below, and it is obviously + needed 0.24 or above for @property syntax) +* C compiler +* Subversion (1.3 and above) with development library +* Apache Portable Runtime development library 1.x (may be installed + by dependency of Subversion's development library) + + +[tested environment] +* Python 3.7.1 / Cython 0.29 / Subversion 1.11.0 / FreeBSD 11 +* Python 2.7.15 / Cython 0.28 / Subversion 1.10.0 / FreeBSD 11 +* Python 2.6.6 / Cython 0.29 / Subversion 1.9.7 / Scientific Linux 6 +* Python 2.6.6 / Cython 0.29 / Subversion 1.8.14 / CentOS 6 (with + ViewVC 1.1.26) + + +[How to build] +(1) Move to src/lib subdirectory (it will be this directory) +(2) Run "python setup.py config". + It creates cfg.py, cython/capi/subversion_1/_svn_api_ver.pxi and +vclib/altsvn/_svn_api_ver.pxi to store build parameter. +Currently, semi-automatic config supports Unix like environment only, +and it may not work correctly. +If it is not work well, you must edit cfg.py and _svn_api_ver.pxi manually. + +In cfg.py, some variables to hold include file directories and library +directory to build module are needed: + + apr_include_dir : path to include files for APR (string) + svn_include_dir : path to include files for Subversion (string) + apr_lib_dir : path to library files for APR (string) + svn_lib_dir : path to library files for Subversion (string) + include_dirs : list of include directory paths of APR and + of Subversion (list of string) + library_dirs : list of library directory paths of APR and + of Subversion (list of string) + +for example: + +-- BEGIN -- +apr_include_dir = "/usr/local/include/apr-1" +svn_include_dir = "/usr/local/include/subversion-1" +apr_lib_dir = "/usr/local/lib" +svn_lib_dir = "/usr/local/lib" +include_dirs = ['/usr/local/include/apr-1', + '/usr/local/include/subversion-1'] +library_dirs = ['/usr/local/lib'] +-- END -- + + The file cython/capi/subversion_1/_svn_api_ver.pxi defines Cython macro +SVN_API_VER and SVN_USE_DOS_PATHS. The content of the file is like: + +-- BEGIN -- +DEF SVN_API_VER = (1, 10) +DEF SVN_USE_DOS_PATHS = 0 +-- END -- + + It is also needed to be exists lib/altsvn/_svn_api_ver.pxi, as just same +content as cython/capi/subversion_1/_svn_api_ver.pxi. so symlink or copy +from cython/capi/subversion_1/_svn_api_ver.pxi. + +(3) run "python setup.py build" + + +[How to use] +(1) run "python setup.py install" in this directory, before run + install-viewvc script. +(2) edit your viewvc.conf to use altsvn as svn access module. + In "[vclib]" section, set 'use_altsvn = 1' (default is "use_altsvn = 0") +(3) run ViewVC + + +[To do] +* more testing. +* improve build and install process + - platforms other than Unix like environment + + +[Notice] + Since this is un-official, personal work of @futatuki on GitHub yet, +please do not ask for this module to ViewVC Group, please use + instead. diff --git a/src/lib/cython/capi/apr_1/__init__.pxd b/src/lib/cython/capi/apr_1/__init__.pxd new file mode 100644 index 00000000..e69de29b diff --git a/src/lib/cython/capi/apr_1/apr.pxd b/src/lib/cython/capi/apr_1/apr.pxd new file mode 100644 index 00000000..20f55102 --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr.pxd @@ -0,0 +1,18 @@ +from libc.stddef cimport size_t +from libc.stdint cimport int16_t,uint16_t,int32_t,uint32_t,int64_t,uint64_t +from posix.types cimport off_t + +cdef extern from "apr.h" nogil: + ctypedef unsigned char apr_byte_t + ctypedef int16_t apr_int16_t + ctypedef uint16_t apr_uint16_t + ctypedef int32_t apr_int32_t + ctypedef uint32_t apr_uint32_t + ctypedef int64_t apr_int64_t + ctypedef uint64_t apr_uint64_t + ctypedef size_t apr_size_t + ctypedef Py_ssize_t apr_ssize_t + ctypedef off_t apr_off_t + ctypedef uint64_t apr_uintptr_t + const char * APR_EOL_STR + diff --git a/src/lib/cython/capi/apr_1/apr_errno.pxd b/src/lib/cython/capi/apr_1/apr_errno.pxd new file mode 100644 index 00000000..ef7bb5b4 --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr_errno.pxd @@ -0,0 +1,12 @@ +from apr_1.apr cimport * + +cdef extern from "apr_errno.h" nogil: + ctypedef int apr_status_t + char * apr_strerror(apr_status_t statcode, char *buf, apr_size_t bufsize) + enum: APR_ENOPOOL + enum: APR_EGENERAL + enum: APR_EOF + enum: APR_ENOMEM + enum: APR_EAGAIN + enum: APR_EINTR + enum: APR_EINPROGRESS diff --git a/src/lib/cython/capi/apr_1/apr_file_info.pxd b/src/lib/cython/capi/apr_1/apr_file_info.pxd new file mode 100644 index 00000000..409ce6d0 --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr_file_info.pxd @@ -0,0 +1,4 @@ +from apr_1.apr cimport apr_int32_t + +cdef extern from "apr_file_info.h" nogil: + ctypedef apr_int32_t apr_fileperms_t diff --git a/src/lib/cython/capi/apr_1/apr_file_io.pxd b/src/lib/cython/capi/apr_1/apr_file_io.pxd new file mode 100644 index 00000000..cd59d87d --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr_file_io.pxd @@ -0,0 +1,21 @@ +cdef extern from "apr_file_io.h" nogil: + ctypedef struct apr_file_t: + pass + # flags -- backcompat + enum: APR_READ + enum: APR_WRITE + enum: APR_CREATE + enum: APR_APPEND + enum: APR_TRUNCATE + enum: APR_BINARY + enum: APR_EXCL + enum: APR_BUFFERED + enum: APR_DELONCLOSE + enum: APR_XTHREAD + enum: APR_SHARELOCK + enum: APR_FILE_NOCLEANUP + enum: APR_SENDFILE_ENABLED + enum: APR_LARGEFILE + # permission -- backcompat + enum: APR_OS_DEFAULT + diff --git a/src/lib/cython/capi/apr_1/apr_general.pxd b/src/lib/cython/capi/apr_1/apr_general.pxd new file mode 100644 index 00000000..3c2a2bfe --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr_general.pxd @@ -0,0 +1,7 @@ +from apr_1.apr_errno cimport * + +cdef extern from "apr_general.h" nogil: + apr_status_t apr_initialize() + void apr_terminate() + void apr_terminate2() + diff --git a/src/lib/cython/capi/apr_1/apr_hash.pxd b/src/lib/cython/capi/apr_1/apr_hash.pxd new file mode 100644 index 00000000..3e87faf3 --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr_hash.pxd @@ -0,0 +1,22 @@ +from apr_1.apr cimport * +from apr_1.apr_pools cimport * + +cdef extern from "apr_hash.h" nogil: + cdef apr_ssize_t APR_HASH_KEY_STRING + ctypedef struct apr_hash_t: + pass + ctypedef struct apr_hash_index_t: + pass + ctypedef unsigned int (*apr_hashfunc_t)(const char *key, apr_ssize_t *klen) + cdef apr_hashfunc_default(const char *key, apr_ssize_t *klen) with gil + apr_hash_t * apr_hash_make(apr_pool_t *pool) + apr_hash_t * apr_hash_make_custom(apr_pool_t *pool, + apr_hashfunc_t hash_func) with gil + void apr_hash_set(apr_hash_t *ht, const void *key, + apr_ssize_t klen, const void *val) + void * apr_hash_get(apr_hash_t *ht, const void *key, apr_ssize_t klen) + apr_hash_index_t * apr_hash_first(apr_pool_t *p, apr_hash_t *ht) + apr_hash_index_t * apr_hash_next(apr_hash_index_t *hi) + void apr_hash_this(apr_hash_index_t *hi, const void **key, + apr_ssize_t *klen, void **val) + void apr_hash_clear(apr_hash_t *ht) diff --git a/src/lib/cython/capi/apr_1/apr_pools.pxd b/src/lib/cython/capi/apr_1/apr_pools.pxd new file mode 100644 index 00000000..622a3af4 --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr_pools.pxd @@ -0,0 +1,11 @@ +from apr_1.apr cimport * +from apr_1.apr_errno cimport * + +cdef extern from "apr_pools.h" nogil: + ctypedef struct apr_pool_t: + pass + apr_status_t apr_pool_initialize() + apr_status_t apr_pool_create(apr_pool_t **newpool, apr_pool_t *parent) + void apr_pool_clear(apr_pool_t *p) + void apr_pool_destroy(apr_pool_t *p) + void * apr_palloc(apr_pool_t *p, apr_size_t size) diff --git a/src/lib/cython/capi/apr_1/apr_strings.pxd b/src/lib/cython/capi/apr_1/apr_strings.pxd new file mode 100644 index 00000000..1f9c88fb --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr_strings.pxd @@ -0,0 +1,4 @@ +from apr_pools cimport apr_pool_t + +cdef extern from "apr_strings.h" nogil: + char* apr_pstrdup(apr_pool_t * p, const char * s) diff --git a/src/lib/cython/capi/apr_1/apr_tables.pxd b/src/lib/cython/capi/apr_1/apr_tables.pxd new file mode 100644 index 00000000..8f9c5546 --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr_tables.pxd @@ -0,0 +1,14 @@ +from apr_1.apr cimport * +from apr_1.apr_pools cimport * + +cdef extern from "apr_tables.h" nogil: + ctypedef struct apr_array_header_t: + apr_pool_t * pool + int elt_size + int nelts + int nalloc + char * elts + apr_array_header_t * apr_array_make( + apr_pool_t * p, int nelts, int elt_size) + void * apr_array_push(apr_array_header_t * arr) + void apr_array_clear(apr_array_header_t * arr) diff --git a/src/lib/cython/capi/apr_1/apr_time.pxd b/src/lib/cython/capi/apr_1/apr_time.pxd new file mode 100644 index 00000000..1815777c --- /dev/null +++ b/src/lib/cython/capi/apr_1/apr_time.pxd @@ -0,0 +1,17 @@ +from apr_1.apr cimport * + +cdef extern from "apr_time.h" nogil: + ctypedef apr_int64_t apr_time_t + ctypedef struct apr_time_exp_t: + apr_int32_t tm_usec + apr_int32_t tm_sec + apr_int32_t tm_min + apr_int32_t tm_hour + apr_int32_t tm_mday + apr_int32_t tm_mon + apr_int32_t tm_year + apr_int32_t tm_wday + apr_int32_t tm_yday + apr_int32_t tm_isdst + apr_int32_t tm_gmtoff + apr_time_t apr_time_now() diff --git a/src/lib/cython/capi/subversion_1/__init__.pxd b/src/lib/cython/capi/subversion_1/__init__.pxd --- /dev/null +++ b/src/lib/cython/capi/apr_1/__init__.pxd @@ -0,0 +1 @@ +# empty file index 00000000..fa81adaf --- /dev/null +++ b/src/lib/cython/capi/subversion_1/__init__.pxd @@ -0,0 +1 @@ +# empty file diff --git a/src/lib/cython/capi/subversion_1/svn_auth.pxd b/src/lib/cython/capi/subversion_1/svn_auth.pxd new file mode 100644 index 00000000..b645c01b --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_auth.pxd @@ -0,0 +1,43 @@ +include "_svn_api_ver.pxi" +from apr_1.apr_pools cimport apr_pool_t +from apr_1.apr_hash cimport apr_hash_t +from apr_1.apr_tables cimport apr_array_header_t +from subversion_1.svn_types cimport svn_error_t, svn_boolean_t + +cdef extern from "svn_auth.h" nogil: + ctypedef struct svn_auth_baton_t: + pass + ctypedef struct svn_auth_provider_t: + const char * cred_kind + svn_error_t * (* first_credentials)( + void ** credentials, void ** iter_baton, void * provider_baton, + apr_hash_t * parameters, const char * realmstring, + apr_pool_t * pool) + svn_error_t * (* next_credentials)( + void ** credentials, void * iter_baton, void * provider_baton, + apr_hash_t * parameters, const char * realmstring, + apr_pool_t * pool) + svn_error_t * (* save_credentials)( + svn_boolean_t saved, void * credentials, void * provider_baton, + apr_hash_t * parameters, const char * realmstring, + apr_pool_t * pool) + ctypedef struct svn_auth_provider_object_t: + const svn_auth_provider_t * vtable + void * provider_baton + void svn_auth_open( + svn_auth_baton_t ** auth_baton, + const apr_array_header_t * providers, apr_pool_t * pool) + IF SVN_API_VER >= (1, 4) and SVN_API_VER < (1, 6): + # Of course, these are also provided for API version 1.6 and above, + # we don't use them directory, use svn_cmdline_create_auth_baton() + # to set them. + void svn_auth_get_simple_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) + void svn_auth_get_username_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) + void svn_auth_get_ssl_server_trust_file_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) + void svn_auth_get_ssl_client_cert_file_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) + void svn_auth_get_ssl_client_cert_pw_file_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) diff --git a/src/lib/cython/capi/subversion_1/svn_checksum.pxd b/src/lib/cython/capi/subversion_1/svn_checksum.pxd new file mode 100644 index 00000000..6989df77 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_checksum.pxd @@ -0,0 +1,20 @@ +include "_svn_api_ver.pxi" + + +cdef extern from "svn_checksum.h" nogil: + IF SVN_API_VER >= (1, 9): + ctypedef enum svn_checksum_kind_t: + svn_checksum_md5 + svn_checksum_sha1 + svn_checksum_fnv1a_32 + svn_checksum_fnv1a_32x4 + ELIF SVN_API_VER >= (1, 6): + ctypedef enum svn_checksum_kind_t: + svn_checksum_md5 + svn_checksum_sha1 + IF SVN_API_VER >= (1, 6): + ctypedef struct svn_checksum_t: + const unsigned char *digest + svn_checksum_kind_t kind + ELSE: + pass diff --git a/src/lib/cython/capi/subversion_1/svn_client.pxd b/src/lib/cython/capi/subversion_1/svn_client.pxd new file mode 100644 index 00000000..abf5710d --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_client.pxd @@ -0,0 +1,378 @@ +include "_svn_api_ver.pxi" +from apr_1.apr cimport apr_int64_t, apr_uint32_t, apr_size_t +from apr_1.apr_pools cimport apr_pool_t +from apr_1.apr_hash cimport apr_hash_t +from apr_1.apr_tables cimport apr_array_header_t +from subversion_1.svn_types cimport * +from subversion_1.svn_opt cimport svn_opt_revision_t +from subversion_1.svn_auth cimport * +from subversion_1.svn_string cimport svn_stringbuf_t +from subversion_1.svn_io cimport svn_stream_t +from subversion_1.svn_wc cimport svn_wc_info_t, svn_wc_schedule_t +IF SVN_API_VER >= (1, 4): + from subversion_1.svn_diff cimport svn_diff_file_options_t + +cdef extern from "svn_client.h" nogil: + IF SVN_API_VER < (1, 4): + # of course, these functions are provided for API version 1.4 and + # above for compatibility, but in those case, we use newer API instead + void svn_client_get_simple_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) + void svn_client_get_username_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) + void svn_client_get_ssl_server_trust_file_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) + void svn_client_get_ssl_client_cert_file_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) + void svn_client_get_ssl_client_cert_pw_file_provider( + svn_auth_provider_object_t ** provider, apr_pool_t * pool) + + ctypedef struct svn_client_proplist_item_t: + svn_stringbuf_t * node_name + apr_hash_t * prop_hash + IF SVN_API_VER >= (1, 8): + ctypedef svn_error_t * (* svn_proplist_receiver2_t)( + void * baton, const char * path, apr_hash_t * prop_hash, + apr_array_header_t * inherited_props, + apr_pool_t * scratch_pool) + IF SVN_API_VER >= (1, 5): + ctypedef svn_error_t * (* svn_proplist_receiver_t)( + void * baton, const char * path, apr_hash_t * prop_hash, + apr_pool_t * scratch_pool) + + # we don't use full feature of structure svn_client_ctx_t, + # and Cython allowes partial declaration of members ... + ctypedef struct svn_client_ctx_t: + svn_auth_baton_t * auth_baton + # svn_wc_notify_func_t notify_func + # void * notify_baton + # svn_client_get_commit_log_t log_msg_func + # void * log_msg_baton + apr_hash_t * config + # svn_cancel_func_t cancel_func + # void * cancel_baton + # svn_wc_notify_func2_t notify_func2 + # void * notify_baton2 + # svn_client_get_commit_log2_t log_msg_func2 + # void * log_msg_baton2 + # svn_ra_progress_notify_func_t progress_func + # void * progress_baton + # svn_client_get_commit_log3_t log_msg_func3 + # void * log_msg_baton3 + # apr_hash_t mimetype_map + # svn_wc_conflict_resolver_func_t conflict_func + # void * conflict_baton + const char * client_name + # svn_wc_conflict_resolver_func2_t conflict_func2 + # void *conflict_baton2 + # svn_wc_context_t * wc_ctx + # svn_ra_check_tunnel_func_t check_tunnel_func + # svn_ra_open_tunnel_func_t open_tunnel_func + # void * tunnel_baton + IF SVN_API_VER >= (1, 8): + svn_error_t * svn_client_create_context2( + svn_client_ctx_t ** ctx, apr_hash_t *cfg_hash, + apr_pool_t *pool) + svn_error_t * svn_client_create_context( + svn_client_ctx_t ** ctx, apr_pool_t *pool) + + # for client_blame*() + IF SVN_API_VER >= (1, 7): + ctypedef svn_error_t * (* svn_client_blame_receiver3_t)( + void * baton, + svn_revnum_t start_revnum, svn_revnum_t end_revnum, + apr_int64_t line_no, + svn_revnum_t revision, apr_hash_t * rev_props, + svn_revnum_t merged_revision, apr_hash_t * merged_rev_props, + const char * merged_path, + const char * line, svn_boolean_t local_change, + apr_pool_t * pool) + svn_error_t * svn_client_blame5( + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * start, + const svn_opt_revision_t * end, + const svn_diff_file_options_t * diff_options, + svn_boolean_t ignore_mime_type, + svn_boolean_t include_merged_revisions, + svn_client_blame_receiver3_t receiver, + void * receiver_baton, svn_client_ctx_t * ctx, + apr_pool_t * pool) + IF SVN_API_VER >= (1, 5): + ctypedef svn_error_t * (* svn_client_blame_receiver2_t)( + void * baton, apr_int64_t line_no, svn_revnum_t revision, + const char * author, const char * date, + svn_revnum_t merged_revision, const char * merged_author, + const char * merged_date, const char * merged_path, + const char * line, apr_pool_t * pool) + svn_error_t * svn_client_blame4( + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * start, + const svn_opt_revision_t * end, + const svn_diff_file_options_t * diff_options, + svn_boolean_t ignore_mime_type, + svn_boolean_t include_merged_revisions, + svn_client_blame_receiver2_t receiver, void * receiver_baton, + svn_client_ctx_t * ctx, apr_pool_t * pool) + ctypedef svn_error_t * (* svn_client_blame_receiver_t)( + void * baton, apr_int64_t line_no, svn_revnum_t revision, + const char * author, const char * date, const char * line, + apr_pool_t * pool) + IF SVN_API_VER >= (1, 4): + svn_error_t * svn_client_blame3( + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * start, + const svn_opt_revision_t * end, + const svn_diff_file_options_t * diff_options, + svn_boolean_t ignore_mime_type, + svn_client_blame_receiver_t receiver, void * receiver_baton, + svn_client_ctx_t * ctx, apr_pool_t * pool) + svn_error_t * svn_client_blame2( + const char * path_or_url, const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * start, const svn_opt_revision_t * end, + svn_client_blame_receiver_t receiver, void * receiver_baton, + svn_client_ctx_t * ctx, apr_pool_t * pool) + + IF SVN_API_VER >= (1, 6): + svn_error_t * svn_client_log5( + const apr_array_header_t * targets, + const svn_opt_revision_t * peg_revision, + const apr_array_header_t * revision_ranges, + int limit, svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_boolean_t include_merged_revisions, + const apr_array_header_t * revprops, + svn_log_entry_receiver_t receiver, + void * receiver_baton, svn_client_ctx_t * ctx, + apr_pool_t * pool) + IF SVN_API_VER >= (1, 5): + svn_error_t * svn_client_log4( + const apr_array_header_t * targets, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * start, + const svn_opt_revision_t * end, + int limit, svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_boolean_t include_merged_revisions, + const apr_array_header_t * revprops, + svn_log_entry_receiver_t receiver, + void * receiver_baton, svn_client_ctx_t * ctx, + apr_pool_t * pool) + IF SVN_API_VER >= (1, 4): + svn_error_t * svn_client_log3( + const apr_array_header_t * targets, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * start, + const svn_opt_revision_t * end, + int limit, svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_log_message_receiver_t receiver, + void * receiver_baton, svn_client_ctx_t * ctx, + apr_pool_t * pool) + svn_error_t * svn_client_log2( + const apr_array_header_t * targets, + const svn_opt_revision_t * start, + const svn_opt_revision_t * end, + int limit, svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_log_message_receiver_t receiver, + void * receiver_baton, svn_client_ctx_t * ctx, apr_pool_t * pool) + + IF SVN_API_VER >= (1, 8): + svn_error_t * svn_client_proplist4( + const char * target, const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, svn_depth_t depth, + const apr_array_header_t * changelists, + svn_boolean_t get_target_inherited_props, + svn_proplist_receiver2_t receiver, void * receiver_baton, + svn_client_ctx_t * ctx, apr_pool_t * scratch_pool) + IF SVN_API_VER >= (1, 5): + svn_error_t * svn_client_proplist3( + const char * target, const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, svn_depth_t depth, + const apr_array_header_t * changelists, + svn_proplist_receiver_t receiver, void * receiver_baton, + svn_client_ctx_t *ctx, apr_pool_t * pool) + IF SVN_API_VER >= (1, 2): + svn_error_t * svn_client_proplist2( + apr_array_header_t ** props, const char * target, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, svn_boolean_t recurse, + svn_client_ctx_t * ctx, apr_pool_t * pool) + + IF SVN_API_VER >= (1, 8): + ctypedef svn_error_t * (* svn_client_list_func2_t)( + void * baton, const char * path, const svn_dirent_t * dirent, + const svn_lock_t * lock, const char * abs_path, + const char * external_parent_url, const char * external_target, + apr_pool_t * scratch_pool) + IF SVN_API_VER >= (1, 4): + ctypedef svn_error_t *(* svn_client_list_func_t)( + void * baton, const char * path, const svn_dirent_t * dirent, + const svn_lock_t * lock, const char * abs_path, + apr_pool_t * pool) + IF SVN_API_VER >= (1, 10): + svn_error_t * svn_client_list4( + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, + const apr_array_header_t *patterns, + svn_depth_t depth, apr_uint32_t dirent_fields, + svn_boolean_t fetch_locks, svn_boolean_t include_externals, + svn_client_list_func2_t list_func, void * baton, + svn_client_ctx_t * ctx, apr_pool_t * scratch_pool) + IF SVN_API_VER >= (1, 8): + svn_error_t * svn_client_list3( + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, + svn_depth_t depth, apr_uint32_t dirent_fields, + svn_boolean_t fetch_locks, svn_boolean_t include_externals, + svn_client_list_func2_t list_func, void * baton, + svn_client_ctx_t * ctx, apr_pool_t * scratch_pool) + IF SVN_API_VER >= (1, 5): + svn_error_t * svn_client_list2( + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, + svn_depth_t depth, apr_uint32_t dirent_fields, + svn_boolean_t fetch_locks, svn_client_list_func_t list_func, + void * baton, svn_client_ctx_t * ctx, apr_pool_t * pool) + IF SVN_API_VER >= (1, 4): + svn_error_t * svn_client_list( + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, + svn_boolean_t recurse, apr_uint32_t dirent_fields, + svn_boolean_t fetch_locks, svn_client_list_func_t list_func, + void * baton, svn_client_ctx_t * ctx, apr_pool_t * pool) + IF SVN_API_VER >= (1, 3): + svn_error_t * svn_client_ls3( + apr_hash_t ** dirents, apr_hash_t ** locks, + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, + svn_boolean_t recurse, svn_client_ctx_t * ctx, + apr_pool_t * pool) + IF SVN_API_VER >= (1, 8): + svn_error_t * svn_client_cat3( + apr_hash_t ** props, svn_stream_t * out, + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, + svn_boolean_t expand_keywords, svn_client_ctx_t * ctx, + apr_pool_t * result_pool, apr_pool_t * scratch_pool) + IF SVN_API_VER >= (1, 2): + svn_error_t * svn_client_cat2( + svn_stream_t * out, const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, + svn_client_ctx_t * ctx, apr_pool_t * pool) + IF SVN_API_VER >= (1, 7): + ctypedef struct svn_client_info2_t: + const char *URL + svn_revnum_t rev + const char *repos_root_URL + const char *repos_UUID + svn_node_kind_t kind + svn_filesize_t size + svn_revnum_t last_changed_rev + apr_time_t last_changed_date + const char *last_changed_author + const svn_lock_t *lock + const svn_wc_info_t *wc_info + ctypedef svn_error_t * (*svn_client_info_receiver2_t)( + void *baton, const char *abspath_or_url, + const svn_client_info2_t *info, apr_pool_t *scratch_pool) + IF SVN_API_VER >= (1, 9): + svn_error_t * svn_client_info4( + const char *abspath_or_url, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *revision, svn_depth_t depth, + svn_boolean_t fetch_excluded, svn_boolean_t fetch_actual_only, + svn_boolean_t include_externals, + const apr_array_header_t *changelists, + svn_client_info_receiver2_t receiver, void *receiver_baton, + svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) + IF SVN_API_VER >= (1, 7): + svn_error_t * svn_client_info3( + const char *abspath_or_url, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *revision, + svn_depth_t depth, + svn_boolean_t fetch_excluded, + svn_boolean_t fetch_actual_only, + const apr_array_header_t *changelists, + svn_client_info_receiver2_t receiver, + void *receiver_batton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) + IF SVN_API_VER >= (1, 5): + ctypedef struct svn_info_t: + const char * URL + svn_revnum_t rev + svn_node_kind_t kind + const char * repos_root_URL + const char * repos_UUID + svn_revnum_t last_changed_rev + apr_time_t last_changed_date + const char * last_changed_author + svn_lock_t * lock + svn_boolean_t has_wc_info + svn_wc_schedule_t schedule + const char * copyfrom_url + svn_revnum_t copyfrom_rev + apr_time_t text_time + apr_time_t prop_time + const char * checksum + const char * conflict_old + const char * conflict_new + const char * conflict_wrk + const char * prejfile + const char * changelist + svn_depth_t depth + apr_size_t working_size + ELSE: + ctypedef struct svn_info_t: + const char * URL + svn_revnum_t rev + svn_node_kind_t kind + const char * repos_root_URL + const char * repos_UUID + svn_revnum_t last_changed_rev + apr_time_t last_changed_date + const char * last_changed_author + svn_locK_t * lock + svn_boolean_t has_wc_info + svn_wc_schedule_t schedule + const char * copyfrom_url + svn_revnum_t copyfrom_rev + apr_time_t text_time + apr_time_t prop_time + const char * checksum + const char * conflict_old + const char * conflict_new + const char * conflict_wrk + const char * prejfile + IF SVN_API_VER >= (1, 2): + ctypedef svn_error_t * (* svn_info_receiver_t)( + void * baton, const char * path, const svn_info_t * info, + apr_pool_t * pool) + IF SVN_API_VER >= (1, 5): + svn_error_t * svn_client_info2( + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, + svn_info_receiver_t receiver, void * receiver_baton, + svn_depth_t depth, const apr_array_header_t * changelists, + svn_client_ctx_t * ctx, apr_pool_t * pool) + IF SVN_API_VER >= (1, 2): + svn_error_t * svn_client_info( + const char * path_or_url, + const svn_opt_revision_t * peg_revision, + const svn_opt_revision_t * revision, + svn_info_receiver_t receiver, void * receiver_baton, + svn_boolean_t recurse, svn_client_ctx_t * ctx, + apr_pool_t * pool) diff --git a/src/lib/cython/capi/subversion_1/svn_cmdline.pxd b/src/lib/cython/capi/subversion_1/svn_cmdline.pxd new file mode 100644 index 00000000..2afecaab --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_cmdline.pxd @@ -0,0 +1,30 @@ +include "_svn_api_ver.pxi" +from apr_1.apr_pools cimport apr_pool_t +from subversion_1.svn_types cimport svn_error_t, svn_boolean_t, \ + svn_cancel_func_t +from subversion_1.svn_config cimport svn_config_t +from subversion_1.svn_auth cimport svn_auth_baton_t + +cdef extern from "svn_cmdline.h" nogil: + IF SVN_API_VER >= (1, 9): + svn_error_t * svn_cmdline_create_auth_baton2( + svn_auth_baton_t ** ab, svn_boolean_t non_interactive, + const char * username, const char * password, + const char * config_dir, svn_boolean_t no_auth_cache, + svn_boolean_t trust_server_cert_unknown_ca, + svn_boolean_t trust_server_cert_cn_mismatch, + svn_boolean_t trust_server_cert_expired, + svn_boolean_t trust_server_cert_not_yet_valid, + svn_boolean_t trust_server_cert_other_failure, + svn_config_t * cfg, svn_cancel_func_t cancel_func, + void * cancel_baton, apr_pool_t * pool) + IF SVN_API_VER >= (1, 6): + svn_error_t * svn_cmdline_create_auth_baton( + svn_auth_baton_t ** ab, svn_boolean_t non_interactive, + const char * username, const char * password, + const char * config_dir, svn_boolean_t no_auth_cache, + svn_boolean_t trust_server_cert, svn_config_t * cfg, + svn_cancel_func_t cancel_func, void * cancel_baton, + apr_pool_t * pool) + ELSE: + pass diff --git a/src/lib/cython/capi/subversion_1/svn_config.pxd b/src/lib/cython/capi/subversion_1/svn_config.pxd new file mode 100644 index 00000000..0345deb1 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_config.pxd @@ -0,0 +1,11 @@ +from apr_1.apr_hash cimport apr_hash_t +from apr_1.apr_pools cimport apr_pool_t +from subversion_1.svn_types cimport svn_error_t + +cdef extern from "svn_string.h" nogil: + ctypedef struct svn_config_t: + pass + const char * SVN_CONFIG_CATEGORY_CONFIG + svn_error_t * svn_config_get_config( + apr_hash_t ** cfg_hash, const char * config_dir, apr_pool_t * pool) + svn_error_t * svn_config_ensure(const char * config_dir, apr_pool_t * pool) diff --git a/src/lib/cython/capi/subversion_1/svn_delta.pxd b/src/lib/cython/capi/subversion_1/svn_delta.pxd new file mode 100644 index 00000000..5dafd4bf --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_delta.pxd @@ -0,0 +1,124 @@ +include "_svn_api_ver.pxi" +from apr_1.apr cimport apr_size_t +from apr_1.apr_pools cimport apr_pool_t +from subversion_1.svn_types cimport * +from subversion_1.svn_string cimport svn_string_t + +cdef extern from "svn_delta.h" nogil: + ctypedef enum svn_delta_action: + svn_txdelta_source + svn_txdelta_target + svn_txdelta_new + ctypedef struct svn_txdelta_op_t: + svn_delta_action action_code + apr_size_t offset + apr_size_t length + ctypedef struct svn_txdelta_window_t: + svn_filesize_t sview_offset + apr_size_t sview_len + apr_size_t tview_len + int num_ops + int src_ops + const svn_txdelta_op_t * ops + const svn_string_t * new_data + ctypedef svn_error_t * (* svn_txdelta_window_handler_t)( + svn_txdelta_window_t * window, void * baton) + ctypedef struct svn_txdelta_stream_t: + pass + IF SVN_API_VER >= (1, 10): + ctypedef svn_error_t * (* svn_txdelta_stream_open_func_t)( + svn_txdelta_stream_t ** txdelta_stream, void * baton, + apr_pool_t * result_pool, apr_pool_t * scratch_pool) + +ctypedef svn_error_t * (* set_target_revision_func_t)( + void * edit_baton, svn_revnum_t target_revision, + apr_pool_t * scratch_pool) +ctypedef svn_error_t * (* open_root_func_t)( + void * edit_baton, svn_revnum_t base_revision, + apr_pool_t *result_pool, void ** root_baton) +ctypedef svn_error_t * (* delete_entry_func_t)( + const char * path, svn_revnum_t revision, void * parent_banton, + apr_pool_t * scratch_pool) +ctypedef svn_error_t * (* add_directory_func_t)( + const char * path, void * parent_baton, const char * copyfrom_path, + svn_revnum_t copyfrom_revision, apr_pool_t * result_pool, + void ** child_baton) +ctypedef svn_error_t * (* open_directory_func_t)( + const char * path, void * parent_baton, svn_revnum_t base_revision, + apr_pool_t * result_pool, void ** child_baton) +ctypedef svn_error_t * (* change_dir_prop_func_t)( + void * dir_baton, const char * name, const svn_string_t * value, + apr_pool_t * scratch_pool) +ctypedef svn_error_t * (* close_directory_func_t)( + void * dir_baton, apr_pool_t * scratch_pool) +ctypedef svn_error_t * (* absent_directory_func_t)( + const char * path, void * parent_baton, apr_pool_t * scratch_pool) +ctypedef svn_error_t * (* add_file_func_t)( + const char * path, void * parent_baton, + const char * copyfrom_path, svn_revnum_t copyfrom_revision, + apr_pool_t * result_pool, void ** file_baton) +ctypedef svn_error_t * (* open_file_func_t)( + const char * path, void * parent_baton, svn_revnum_t base_revision, + apr_pool_t * result_pool, void ** file_baton) +ctypedef svn_error_t * ( * apply_textdelta_func_t)( + void * file_baton, const char * base_checksum, + apr_pool_t * result_pool, svn_txdelta_window_handler_t * handler, + void ** handler_baton) +ctypedef svn_error_t * ( * change_file_prop_func_t)( + void * file_baton, const char * name, const svn_string_t * value, + apr_pool_t * scratch_pool) +ctypedef svn_error_t * ( * close_file_func_t)( + void * file_baton, const char * text_checksum, + apr_pool_t * scratch_pool) +ctypedef svn_error_t * ( * absent_file_func_t)( + const char * path, void * parent_baton, apr_pool_t * scratch_pool) +ctypedef svn_error_t * ( * close_edit_func_t)( + void * edit_baton, apr_pool_t * scratch_pool) +ctypedef svn_error_t * ( * abort_edit_func_t)( + void * edit_baton, apr_pool_t * scratch_pool) +IF SVN_API_VER >= (1, 10): + ctypedef svn_error_t * ( * apply_textdelta_stream_func_t)( + const svn_delta_editor_t * editor, void * file_baton, + const char * base_checksum, + svn_txdelta_stream_open_func_t open_func, + void * open_baton, apr_pool_t * scratch_pool) + +cdef extern from "svn_delta.h" nogil: + IF SVN_API_VER >= (1, 10): + ctypedef struct svn_delta_editor_t: + set_target_revision_func_t set_target_revision + open_root_func_t open_root + delete_entry_func_t delete_entry + add_directory_func_t add_directory + open_directory_func_t open_directory + change_dir_prop_func_t change_dir_prop + close_directory_func_t close_directory + absent_directory_func_t absent_directory + add_file_func_t add_file + open_file_func_t open_file + apply_textdelta_func_t apply_textdelta + change_file_prop_func_t change_file_prop + close_file_func_t close_file + absent_file_func_t absent_file + close_edit_func_t close_edit + abort_edit_func_t abort_edit + apply_textdelta_stream_func_t apply_textdelta_stream + ELSE: + ctypedef struct svn_delta_editor_t: + set_target_revision_func_t set_target_revision + open_root_func_t open_root + delete_entry_func_t delete_entry + add_directory_func_t add_directory + open_directory_func_t open_directory + change_dir_prop_func_t change_dir_prop + close_directory_func_t close_directory + absent_directory_func_t absent_directory + add_file_func_t add_file + open_file_func_t open_file + apply_textdelta_func_t apply_textdelta + change_file_prop_func_t change_file_prop + close_file_func_t close_file + absent_file_func_t absent_file + close_edit_func_t close_edit + abort_edit_func_t abort_edit + svn_delta_editor_t * svn_delta_default_editor(apr_pool_t * pool) diff --git a/src/lib/cython/capi/subversion_1/svn_diff.pxd b/src/lib/cython/capi/subversion_1/svn_diff.pxd new file mode 100644 index 00000000..21f71838 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_diff.pxd @@ -0,0 +1,27 @@ +include "_svn_api_ver.pxi" +from apr_1.apr_pools cimport apr_pool_t +from subversion_1.svn_types cimport svn_boolean_t + +IF SVN_API_VER >= (1, 4): + cdef extern from "svn_diff.h" nogil: + ctypedef enum svn_diff_file_ignore_space_t: + svn_diff_file_ignore_space_none + svn_diff_file_ignore_space_change + svn_diff_file_ignore_space_all + IF SVN_API_VER >= (1, 9): + ctypedef struct svn_diff_file_options_t: + svn_diff_file_ignore_space_t ignore_space + svn_boolean_t ignore_eol_style + svn_boolean_t show_c_function + int context_size + ELIF SVN_API_VER >= (1, 5): + ctypedef struct svn_diff_file_options_t: + svn_diff_file_ignore_space_t ignore_space + svn_boolean_t ignore_eol_style + svn_boolean_t show_c_function + ELIF SVN_API_VER >= (1, 4): + ctypedef struct svn_diff_file_options_t: + svn_diff_file_ignore_space_t ignore_space + svn_boolean_t ignore_eol_style + svn_diff_file_options_t * svn_diff_file_options_create( + apr_pool_t * pool) diff --git a/src/lib/cython/capi/subversion_1/svn_dirent_uri.pxd b/src/lib/cython/capi/subversion_1/svn_dirent_uri.pxd new file mode 100644 index 00000000..865420d0 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_dirent_uri.pxd @@ -0,0 +1,16 @@ +from apr_1.apr_pools cimport apr_pool_t +from subversion_1.svn_types cimport svn_error_t + +include "_svn_api_ver.pxi" + +cdef extern from "svn_dirent_uri.h" nogil: + IF SVN_API_VER >= (1, 6): + const char * svn_dirent_canonicalize( + const char *dirent, apr_pool_t *result_pool) + IF SVN_API_VER >= (1, 7): + const char * svn_uri_canonicalize( + const char *uri, apr_pool_t *result_pool) + svn_error_t * svn_uri_get_dirent_from_file_url( + const char **dirent, const char *url, apr_pool_t *result_pool) + svn_error_t * svn_uri_get_file_url_from_dirent( + const char **url, const char *dirent, apr_pool_t *result_pool) diff --git a/src/lib/cython/capi/subversion_1/svn_error.pxd b/src/lib/cython/capi/subversion_1/svn_error.pxd new file mode 100644 index 00000000..a6038c15 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_error.pxd @@ -0,0 +1,17 @@ +include "_svn_api_ver.pxi" +from apr_1.apr cimport apr_size_t +from apr_1.apr_errno cimport apr_status_t +from subversion_1.svn_types cimport svn_error_t + +cdef extern from "svn_error.h" nogil: + char * svn_strerror(apr_status_t statcode, char * buf, apr_size_t bufsize) + IF SVN_API_VER >= (1, 8): + const char * svn_error_symbolic_name(apr_status_t statcode) + IF SVN_API_VER >= (1, 4): + const char * svn_err_best_message( + const svn_error_t *err, char * buf, apr_size_t bufsize) + svn_error_t * svn_error_create(apr_status_t apr_err, svn_error_t * child, + const char * message) + void svn_error_compose(svn_error_t * chain, svn_error_t * new_err) + svn_error_t * svn_error_dup(const svn_error_t * error) + void svn_error_clear(svn_error_t * error) diff --git a/src/lib/cython/capi/subversion_1/svn_error_codes.pxd b/src/lib/cython/capi/subversion_1/svn_error_codes.pxd new file mode 100644 index 00000000..9cf5e6ed --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_error_codes.pxd @@ -0,0 +1,14 @@ +include "_svn_api_ver.pxi" + +cdef extern from "svn_error_codes.h": + enum: SVN_NO_ERROR + enum: SVN_ERR_BASE + enum: SVN_ERR_STREAM_NOT_SUPPORTED + enum: SVN_ERR_FS_NOT_FOUND + enum: SVN_ERR_CLIENT_IS_BINARY_FILE + enum: SVN_ERR_UNSUPPORTED_FEATURE + enum: SVN_ERR_SWIG_PY_EXCEPTION_SET + enum: SVN_ERR_CANCELLED + enum: SVN_ERR_ASSERTION_FAIL + IF SVN_API_VER >= (1, 5): + enum: SVN_ERR_CEASE_INVOCATION diff --git a/src/lib/cython/capi/subversion_1/svn_fs.pxd b/src/lib/cython/capi/subversion_1/svn_fs.pxd new file mode 100644 index 00000000..addbdd02 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_fs.pxd @@ -0,0 +1,152 @@ +include "_svn_api_ver.pxi" +from apr_1.apr_pools cimport apr_pool_t +from apr_1.apr_hash cimport apr_hash_t +from subversion_1.svn_types cimport * +from subversion_1.svn_string cimport svn_string_t +from subversion_1.svn_io cimport svn_stream_t + +cdef extern from "svn_fs.h" nogil: + ctypedef struct svn_fs_t: + pass + ctypedef struct svn_fs_root_t: + pass + ctypedef struct svn_fs_id_t: + pass + int svn_fs_compare_ids(const svn_fs_id_t * a, const svn_fs_id_t * b) + svn_error_t * svn_fs_revision_root( + svn_fs_root_t ** root_p, svn_fs_t * fs, svn_revnum_t rev, + apr_pool_t * pool) + void svn_fs_close_root(svn_fs_root_t * root) + svn_boolean_t svn_fs_is_revision_root(svn_fs_root_t * root) + svn_revnum_t svn_fs_revision_root_revision(svn_fs_root_t * root) + ctypedef enum svn_fs_path_change_kind_t: + svn_fs_path_change_modify = 0 + svn_fs_path_change_add + svn_fs_path_change_delete + svn_fs_path_change_replace + svn_fs_path_change_reset + IF SVN_API_VER >= (1, 10): + ctypedef struct svn_fs_path_change3_t: + svn_string_t path + svn_fs_path_change_kind_t change_kind + svn_node_kind_t node_kind + svn_boolean_t text_mod + svn_boolean_t prop_mod + svn_tristate_t mergeinfo_mod + svn_boolean_t copyfrom_known + const char * copyfrom_path + IF SVN_API_VER >= (1, 9): + ctypedef struct svn_fs_path_change2_t: + const svn_fs_id_t * node_rev_id + svn_fs_path_change_kind_t change_kind + svn_boolean_t text_mod + svn_boolean_t prop_mod + svn_node_kind_t node_kind + svn_boolean_t copyfrom_known + svn_revnum_t copyfrom_rev + const char * copyfrom_path + svn_tristate_t mergeinfo_mod + ELIF SVN_API_VER >= (1, 6): + ctypedef struct svn_fs_path_change2_t: + const svn_fs_id_t * node_rev_id + svn_fs_path_change_kind_t change_kind + svn_boolean_t text_mod + svn_boolean_t prop_mod + svn_node_kind_t node_kind + svn_boolean_t copyfrom_known + svn_revnum_t copyfrom_rev + const char * copyfrom_path + ctypedef struct svn_fs_path_change_t: + const svn_fs_id_t * node_rev_id + svn_fs_path_change_kind_t change_kind + svn_boolean_t text_mod + svn_boolean_t prop_mod + IF SVN_API_VER >= (1, 10): + ctypedef struct svn_fs_path_change_iterator_t: + pass + svn_error_t * svn_fs_path_change_get( + svn_fs_path_change3_t ** change, + svn_fs_path_change_iterator_t * iterator) + svn_error_t * svn_fs_paths_changed3( + svn_fs_path_change_iterator_t ** iterator, + svn_fs_root_t * root, + apr_pool_t * result_pool, apr_pool_t * scratch_pool) + IF SVN_API_VER >= (1, 6): + # deprecated (in 1.10.) + svn_error_t * svn_fs_paths_changed2( + apr_hash_t ** changed_paths2_p, svn_fs_root_t * root, + apr_pool_t * pool) + # deprecated (in 1.6.) + svn_error_t * svn_fs_paths_changed( + apr_hash_t ** changed_paths_p, svn_fs_root_t * root, + apr_pool_t * pool) + svn_error_t * svn_fs_check_path( + svn_node_kind_t * kind_p, svn_fs_root_t * root, + const char * path, apr_pool_t * pool) + ctypedef struct svn_fs_history_t: + pass + IF SVN_API_VER >= (1, 9): + svn_error_t * svn_fs_node_history2( + svn_fs_history_t ** history_p, svn_fs_root_t * root, + const char * path, apr_pool_t * result_pool, + apr_pool_t * scratch_pool) + svn_error_t * svn_fs_history_prev2( + svn_fs_history_t ** prev_history_p, svn_fs_history_t * history, + svn_boolean_t cross_copies, apr_pool_t *result_pool, + apr_pool_t *scratch_pool) + # deprecated (in 1.9.) + svn_error_t * svn_fs_node_history( + svn_fs_history_t ** history_p, svn_fs_root_t * root, + const char * path, apr_pool_t * pool) + # deprecated (in 1.9.) + svn_error_t * svn_fs_history_prev( + svn_fs_history_t ** prev_history_p, svn_fs_history_t * history, + svn_boolean_t cross_copies, apr_pool_t *pool) + svn_error_t * svn_fs_history_location( + const char ** path, svn_revnum_t * revision, + svn_fs_history_t * history, apr_pool_t * pool) + svn_error_t * svn_fs_is_dir( + svn_boolean_t * is_dir, svn_fs_root_t * root, + const char * path, apr_pool_t * pool) + svn_error_t * svn_fs_is_file( + svn_boolean_t * is_file, svn_fs_root_t * root, + const char * path, apr_pool_t * pool) + svn_error_t * svn_fs_node_id( + const svn_fs_id_t ** id_p, svn_fs_root_t * root, + const char * path, apr_pool_t * pool) + svn_error_t * svn_fs_node_created_rev( + svn_revnum_t * revision, svn_fs_root_t * root, + const char * path, apr_pool_t * pool); + svn_error_t * svn_fs_node_proplist( + apr_hash_t ** table_p, svn_fs_root_t * root, + const char * path, apr_pool_t * pool); + svn_error_t * svn_fs_copied_from( + svn_revnum_t * rev_p, const char ** path_p, svn_fs_root_t * root, + const char * path, apr_pool_t * pool) + ctypedef struct svn_fs_dirent_t: + const char * name + const svn_fs_id_t * id + svn_node_kind_t kind + svn_error_t * svn_fs_dir_entries( + apr_hash_t ** entries_p, svn_fs_root_t * root, + const char * path, apr_pool_t *pool) + svn_error_t * svn_fs_file_length( + svn_filesize_t * length_p, svn_fs_root_t * root, + const char * path, apr_pool_t *pool) + svn_error_t * svn_fs_file_contents( + svn_stream_t ** contents, svn_fs_root_t * root, + const char * path, apr_pool_t *pool) + svn_error_t * svn_fs_youngest_rev( + svn_revnum_t * youngest_p, svn_fs_t * fs, apr_pool_t * pool) + IF SVN_API_VER >= (1, 10): + svn_error_t * svn_fs_revision_proplist2( + apr_hash_t ** table_p, svn_fs_t * fs, svn_revnum_t rev, + svn_boolean_t refresh, apr_pool_t * result_pool, + apr_pool_t *scratch_pool) + # deprecated (in 1.10.) + svn_error_t * svn_fs_revision_proplist( + apr_hash_t ** table_p, svn_fs_t * fs, svn_revnum_t rev, + apr_pool_t * pool) + svn_error_t * svn_fs_get_lock( + svn_lock_t ** lock, svn_fs_t * fs, const char * path, + apr_pool_t * pool) diff --git a/src/lib/cython/capi/subversion_1/svn_io.pxd b/src/lib/cython/capi/subversion_1/svn_io.pxd new file mode 100644 index 00000000..2a551464 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_io.pxd @@ -0,0 +1,99 @@ +include "_svn_api_ver.pxi" +from apr_1.apr cimport apr_size_t, apr_int32_t +from apr_1.apr_file_io cimport apr_file_t +from apr_1.apr_file_info cimport apr_fileperms_t +from apr_1.apr_pools cimport apr_pool_t +from subversion_1.svn_types cimport svn_error_t, svn_boolean_t +from subversion_1.svn_string cimport svn_stringbuf_t + +cdef extern from "svn_io.h" nogil: + ctypedef struct svn_stream_t: + pass + ctypedef svn_error_t * (* svn_read_fn_t)( + void * baton, char * buffer, apr_size_t * len) + IF SVN_API_VER >= (1, 7): + ctypedef svn_error_t * (* svn_stream_skip_fn_t)( + void * baton, apr_size_t len) + ctypedef svn_error_t * (* svn_write_fn_t)( + void * baton, const char * data, apr_size_t * len) + ctypedef svn_error_t * (* svn_close_fn_t)(void * baton) + IF SVN_API_VER >= (1, 7): + ctypedef struct svn_stream_mark_t: + pass + ctypedef svn_error_t * (* svn_stream_mark_fn_t)( + void * baton, svn_stream_mark_t ** mark, apr_pool_t * pool) + ctypedef svn_error_t *(*svn_stream_seek_fn_t)( + void * baton, const svn_stream_mark_t * mark) + IF SVN_API_VER >= (1, 9): + ctypedef svn_error_t * (* svn_stream_data_available_fn_t)( + void * baton, svn_boolean_t * data_available) + IF SVN_API_VER >= (1, 10): + ctypedef svn_error_t *(*svn_stream_readline_fn_t)( + void * baton, svn_stringbuf_t ** stringbuf, const char * eol, + svn_boolean_t * eof, apr_pool_t * pool) + svn_stream_t * svn_stream_create(void * baton, apr_pool_t * pool) + void svn_stream_set_baton(svn_stream_t * stream, void * baton) + IF SVN_API_VER >= (1, 9): + void svn_stream_set_read2( + svn_stream_t * stream, svn_read_fn_t read_fn, + svn_read_fn_t read_full_fn) + void svn_stream_set_read(svn_stream_t * stream, svn_read_fn_t read_fn) + IF SVN_API_VER >= (1, 7): + void svn_stream_set_skip( + svn_stream_t * stream, svn_stream_skip_fn_t skip_fn) + void svn_stream_set_write(svn_stream_t * stream, svn_write_fn_t write_fn) + void svn_stream_set_close(svn_stream_t * stream, svn_close_fn_t close_fn) + IF SVN_API_VER >= (1, 7): + void svn_stream_set_mark( + svn_stream_t * stream, svn_stream_mark_fn_t mark_fn) + void svn_stream_set_seek( + svn_stream_t * stream, svn_stream_seek_fn_t seek_fn) + IF SVN_API_VER >= (1, 9): + void svn_stream_set_data_available( + svn_stream_t * stream, + svn_stream_data_available_fn_t data_available) + IF SVN_API_VER >= (1, 10): + void svn_stream_set_readline( + svn_stream_t * stream, svn_stream_readline_fn_t readline_fn) + + IF SVN_API_VER >= (1, 9): + svn_error_t * svn_stream_read_full( + svn_stream_t * stream, char * buffer, apr_size_t *len) + svn_error_t * svn_stream_read2( + svn_stream_t * stream, char * buffer, apr_size_t *len) + svn_error_t * svn_stream_read( + svn_stream_t * stream, char * buffer, apr_size_t *len) + IF SVN_API_VER >= (1, 7): + svn_error_t * svn_stream_skip( + svn_stream_t * stream, apr_size_t len) + svn_error_t * svn_stream_write( + svn_stream_t * stream, const char * buffer, apr_size_t *len) + svn_error_t * svn_stream_close(svn_stream_t * stream) + IF SVN_API_VER >= (1, 7): + svn_error_t * svn_stream_reset(svn_stream_t * stream) + svn_error_t * svn_stream_mark( + svn_stream_t * stream, svn_stream_mark_t ** mark, + apr_pool_t * pool) + svn_error_t * svn_stream_seek( + svn_stream_t * stream, const svn_stream_mark_t * mark) + IF SVN_API_VER >= (1, 9): + svn_error_t * svn_stream_data_available( + svn_stream_t * stream, svn_boolean_t * data_available) + svn_error_t * svn_stream_readline( + svn_stream_t * stream, svn_stringbuf_t ** stringbuf, + const char * eol, svn_boolean_t * eof, apr_pool_t * pool) + IF SVN_API_VER >= (1, 6): + svn_error_t * svn_stream_open_readonly( + svn_stream_t ** stream, const char * path, + apr_pool_t * result_pool, apr_pool_t * scratch_pool) + + # used only for implementing svn_stream_open_readonly() + svn_error_t * svn_io_file_open( + apr_file_t ** new_file, const char * fname, apr_int32_t flag, + apr_fileperms_t perm, apr_pool_t * pool) + svn_error_t * svn_io_file_close(apr_file_t *file, apr_pool_t *pool) + IF SVN_API_VER >= (1, 4): + svn_stream_t * svn_stream_from_aprfile2( + apr_file_t * file, svn_boolean_t disown, apr_pool_t * pool) + svn_stream_t * svn_stream_from_aprfile( + apr_file_t * file, apr_pool_t * pool) diff --git a/src/lib/cython/capi/subversion_1/svn_opt.pxd b/src/lib/cython/capi/subversion_1/svn_opt.pxd new file mode 100644 index 00000000..244eb8a3 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_opt.pxd @@ -0,0 +1,22 @@ +from subversion_1.svn_types cimport svn_revnum_t +from apr_1.apr_time cimport apr_time_t + +cdef extern from "svn_opt.h": + cdef enum svn_opt_revision_kind: + svn_opt_revision_unspecified + svn_opt_revision_number + svn_opt_revision_date + svn_opt_revision_committed + svn_opt_revision_previous + svn_opt_revision_base + svn_opt_revision_working + svn_opt_revision_head + ctypedef union svn_opt_revision_value_t: + svn_revnum_t number + apr_time_t date + ctypedef struct svn_opt_revision_t: + svn_opt_revision_kind kind + svn_opt_revision_value_t value + ctypedef struct svn_opt_revision_range_t: + svn_opt_revision_t start + svn_opt_revision_t end diff --git a/src/lib/cython/capi/subversion_1/svn_path.pxd b/src/lib/cython/capi/subversion_1/svn_path.pxd new file mode 100644 index 00000000..170f71c1 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_path.pxd @@ -0,0 +1,8 @@ +from apr_1.apr_pools cimport apr_pool_t +from subversion_1.svn_types cimport svn_boolean_t + +cdef extern from "svn_path.h" nogil: + const char * svn_path_canonicalize(const char * path, apr_pool_t * pool) + svn_boolean_t svn_path_is_url(const char *path) + const char * svn_path_uri_encode(const char * path, apr_pool_t * pool) + diff --git a/src/lib/cython/capi/subversion_1/svn_props.pxd b/src/lib/cython/capi/subversion_1/svn_props.pxd new file mode 100644 index 00000000..afe98669 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_props.pxd @@ -0,0 +1,6 @@ +cdef extern from "svn_props.h": + const char * SVN_PROP_REVISION_LOG + const char * SVN_PROP_REVISION_AUTHOR + const char * SVN_PROP_REVISION_DATE + const char * SVN_PROP_EXECUTABLE + const char * SVN_PROP_SPECIAL diff --git a/src/lib/cython/capi/subversion_1/svn_ra.pxd b/src/lib/cython/capi/subversion_1/svn_ra.pxd new file mode 100644 index 00000000..d7689c54 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_ra.pxd @@ -0,0 +1,134 @@ +include "_svn_api_ver.pxi" +from apr_1.apr cimport apr_off_t +from apr_1.apr_pools cimport apr_pool_t +from apr_1.apr_hash cimport apr_hash_t +from apr_1.apr_tables cimport apr_array_header_t +from apr_1.apr_file_io cimport apr_file_t +from subversion_1.svn_types cimport * +from subversion_1.svn_auth cimport svn_auth_baton_t +from subversion_1.svn_string cimport svn_string_t +from subversion_1.svn_io cimport svn_stream_t +IF SVN_API_VER >= (1, 6): + from subversion_1.svn_checksum cimport svn_checksum_t + +cdef extern from "svn_ra.h" nogil: + ctypedef svn_error_t * (* svn_ra_get_wc_prop_func_t)( + void * baton, const char * path, const char * name, + const svn_string_t ** value, apr_pool_t *pool) + ctypedef svn_error_t * (* svn_ra_set_wc_prop_func_t)( + void * baton, const char * path, const char * name, + const svn_string_t * value, apr_pool_t * pool) + ctypedef svn_error_t * (* svn_ra_push_wc_prop_func_t)( + void * baton, const char *path, const char * name, + const svn_string_t * value, apr_pool_t * pool) + ctypedef svn_error_t * (* svn_ra_invalidate_wc_props_func_t)( + void * baton, const char * path, const char * name, + apr_pool_t * pool) + IF SVN_API_VER >= (1, 3): + ctypedef void ( * svn_ra_progress_notify_func_t)( + apr_off_t progress, apr_off_t total, void * baton, + apr_pool_t * pool) + IF SVN_API_VER >= (1, 8): + ctypedef svn_error_t * (* svn_ra_get_wc_contents_func_t)( + void * baton, svn_stream_t ** contents, + const svn_checksum_t * checksum, apr_pool_t * pool) + IF SVN_API_VER >= (1, 5): + ctypedef svn_error_t * ( * svn_ra_get_client_string_func_t)( + void * baton, const char ** name, apr_pool_t * pool) + IF SVN_API_VER >= (1, 9): + ctypedef svn_boolean_t ( * svn_ra_check_tunnel_func_t)( + void * tunnel_baton, const char * tunnel_name) + ctypedef void (* svn_ra_close_tunnel_func_t)( + void * close_baton, void * tunnel_baton) + ctypedef svn_error_t * ( * svn_ra_open_tunnel_func_t)( + svn_stream_t ** request, svn_stream_t ** response, + svn_ra_close_tunnel_func_t * close_func, void ** close_baton, + void * tunnel_baton, const char * tunnel_name, + const char * user, const char * hostname, int port, + svn_cancel_func_t cancel_func, void * cancel_baton, + apr_pool_t * pool) + ctypedef struct svn_ra_callbacks2_t: + svn_error_t * (* open_tmp_file)( + apr_file_t ** fp, void * callback_baton, apr_pool_t * pool) + svn_auth_baton_t * auth_baton + svn_ra_get_wc_prop_func_t get_wc_prop + svn_ra_set_wc_prop_func_t set_wc_prop + svn_ra_push_wc_prop_func_t push_wc_prop + svn_ra_invalidate_wc_props_func_t invalidate_wc_props + svn_ra_progress_notify_func_t progress_func + svn_cancel_func_t cancel_func + svn_ra_get_client_string_func_t get_client_string + svn_ra_get_wc_contents_func_t get_wc_contents + svn_ra_check_tunnel_func_t check_tunnel_func + svn_ra_open_tunnel_func_t open_tunnel_func + void * tunnel_baton + ELIF SVN_API_VER == (1, 8): + ctypedef struct svn_ra_callbacks2_t: + svn_error_t * (* open_tmp_file)( + apr_file_t ** fp, void * callback_baton, apr_pool_t * pool) + svn_auth_baton_t * auth_baton + svn_ra_get_wc_prop_func_t get_wc_prop + svn_ra_set_wc_prop_func_t set_wc_prop + svn_ra_push_wc_prop_func_t push_wc_prop + svn_ra_invalidate_wc_props_func_t invalidate_wc_props + svn_ra_progress_notify_func_t progress_func + svn_cancel_func_t cancel_func + svn_ra_get_client_string_func_t get_client_string + svn_ra_get_wc_contents_func_t get_wc_contents + ELIF SVN_API_VER >= (1, 5): + ctypedef struct svn_ra_callbacks2_t: + svn_error_t * (* open_tmp_file)( + apr_file_t ** fp, void * callback_baton, apr_pool_t * pool) + svn_auth_baton_t * auth_baton + svn_ra_get_wc_prop_func_t get_wc_prop + svn_ra_set_wc_prop_func_t set_wc_prop + svn_ra_push_wc_prop_func_t push_wc_prop + svn_ra_invalidate_wc_props_func_t invalidate_wc_props + svn_ra_progress_notify_func_t progress_func + svn_cancel_func_t cancel_func + svn_ra_get_client_string_func_t get_client_string + ELIF SVN_API_VER >= (1, 3): + ctypedef struct svn_ra_callbacks2_t: + svn_error_t * (* open_tmp_file)( + apr_file_t ** fp, void * callback_baton, apr_pool_t * pool) + svn_auth_baton_t * auth_baton + svn_ra_get_wc_prop_func_t get_wc_prop + svn_ra_set_wc_prop_func_t set_wc_prop + svn_ra_push_wc_prop_func_t push_wc_prop + svn_ra_invalidate_wc_props_func_t invalidate_wc_props + svn_ra_progress_notify_func_t progress_func + + svn_error_t * svn_ra_initialize(apr_pool_t * pool) + IF SVN_API_VER >= (1, 3): + svn_error_t * svn_ra_create_callbacks( + svn_ra_callbacks2_t ** callbacks, apr_pool_t * pool) + ctypedef struct svn_ra_session_t: + pass + IF SVN_API_VER >= (1, 7): + svn_error_t * svn_ra_open4( + svn_ra_session_t ** session_p, const char ** corrected_url, + const char * repos_URL, const char * uuid, + const svn_ra_callbacks2_t * callbacks, + void * callback_baton, apr_hash_t * config, apr_pool_t * pool) + IF SVN_API_VER >= (1, 5): + svn_error_t * svn_ra_open3( + svn_ra_session_t ** session_p, const char * repos_URL, + const char * uuid, const svn_ra_callbacks2_t * callbacks, + void * callback_baton, apr_hash_t * config, apr_pool_t * pool) + IF SVN_API_VER >= (1, 3): + svn_error_t * svn_ra_open2( + svn_ra_session_t ** session_p, const char * repos_URL, + const svn_ra_callbacks2_t * callbacks, void * callback_baton, + apr_hash_t * config, apr_pool_t *pool) + svn_error_t * svn_ra_check_path( + svn_ra_session_t * session, const char * path, + svn_revnum_t revision, svn_node_kind_t * kind, + apr_pool_t * pool) + svn_error_t * svn_ra_get_locations( + svn_ra_session_t * session, apr_hash_t ** locations, + const char * path, svn_revnum_t peg_revision, + const apr_array_header_t * location_revisions, + apr_pool_t * pool) + svn_error_t * svn_ra_get_latest_revnum( + svn_ra_session_t * session, svn_revnum_t * latest_revnum, + apr_pool_t * pool) diff --git a/src/lib/cython/capi/subversion_1/svn_repos.pxd b/src/lib/cython/capi/subversion_1/svn_repos.pxd new file mode 100644 index 00000000..7f8cf5ea --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_repos.pxd @@ -0,0 +1,52 @@ +include "_svn_api_ver.pxi" +from apr_1.apr_pools cimport apr_pool_t +from apr_1.apr_hash cimport apr_hash_t +from apr_1.apr_tables cimport apr_array_header_t +from subversion_1.svn_types cimport svn_error_t, svn_boolean_t, svn_revnum_t +from subversion_1.svn_fs cimport svn_fs_t, svn_fs_root_t +from subversion_1.svn_delta cimport svn_delta_editor_t + +cdef extern from "svn_repos.h" nogil: + ctypedef svn_error_t * (* svn_repos_authz_func_t)( + svn_boolean_t * allowed, svn_fs_root_t * root, + const char * path, void * baton, apr_pool_t * pool) + ctypedef struct svn_repos_t: + pass + IF SVN_API_VER >= (1, 9): + svn_error_t * svn_repos_open3( + svn_repos_t ** repos_p, const char * path, + apr_hash_t * fs_config, apr_pool_t * result_pool, + apr_pool_t * scratch_pool) + IF SVN_API_VER >= (1, 7): + svn_error_t * svn_repos_open2( + svn_repos_t ** repos_p, const char * path, + apr_hash_t * fs_config, apr_pool_t * pool) + svn_error_t * svn_repos_open( + svn_repos_t ** repos_p, const char * path, apr_pool_t *) + svn_fs_t * svn_repos_fs(svn_repos_t * repos) + + IF SVN_API_VER >= (1, 4): + svn_error_t * svn_repos_replay2( + svn_fs_root_t * root, const char * base_dir, + svn_revnum_t low_water_mark, svn_boolean_t send_deltas, + const svn_delta_editor_t * editor, void * edit_baton, + svn_repos_authz_func_t authz_read_func, + void * authz_read_baton, apr_pool_t * pool) + svn_error_t * svn_repos_replay( + svn_fs_root_t * root, const svn_delta_editor_t * editor, + void * edit_baton, apr_pool_t * pool) + ctypedef svn_error_t * (* svn_repos_history_func_t)( + void *baton, const char *path, svn_revnum_t revision, + apr_pool_t *pool) + svn_error_t * svn_repos_history2( + svn_fs_t * fs, const char * path, + svn_repos_history_func_t history_func, void * history_baton, + svn_repos_authz_func_t authz_read_func, void * authz_read_baton, + svn_revnum_t start, svn_revnum_t end, svn_boolean_t cross_copies, + apr_pool_t * pool) + svn_error_t * svn_repos_trace_node_locations( + svn_fs_t * fs, apr_hash_t ** locations, const char *fs_path, + svn_revnum_t peg_revision, + const apr_array_header_t *location_revisions, + svn_repos_authz_func_t authz_read_func, void *authz_read_baton, + apr_pool_t *pool) diff --git a/src/lib/cython/capi/subversion_1/svn_string.pxd b/src/lib/cython/capi/subversion_1/svn_string.pxd new file mode 100644 index 00000000..3c37df7d --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_string.pxd @@ -0,0 +1,16 @@ +from apr_1.apr cimport apr_size_t +from apr_1.apr_pools cimport apr_pool_t + +cdef extern from "svn_string.h" nogil: + ctypedef struct svn_string_t: + const char * data + apr_size_t len + ctypedef struct svn_stringbuf_t: + apr_pool_t * pool + char * data + apr_size_t len + apr_size_t blocksize + svn_stringbuf_t * svn_stringbuf_create( + const char * cstring, apr_pool_t * pool) + svn_stringbuf_t * svn_stringbuf_ncreate( + const char * bytes, apr_size_t size, apr_pool_t *pool) diff --git a/src/lib/cython/capi/subversion_1/svn_time.pxd b/src/lib/cython/capi/subversion_1/svn_time.pxd new file mode 100644 index 00000000..9c906746 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_time.pxd @@ -0,0 +1,7 @@ +from apr_1.apr_pools cimport apr_pool_t +from apr_1.apr_time cimport apr_time_t +from subversion_1.svn_types cimport svn_error_t + +cdef extern from "svn_time.h" nogil: + svn_error_t * svn_time_from_cstring( + apr_time_t *when, const char * data, apr_pool_t *pool) diff --git a/src/lib/cython/capi/subversion_1/svn_types.pxd b/src/lib/cython/capi/subversion_1/svn_types.pxd new file mode 100644 index 00000000..80f543e1 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_types.pxd @@ -0,0 +1,133 @@ +from apr_1.apr cimport apr_int64_t +from apr_1.apr_errno cimport apr_status_t +from apr_1.apr_pools cimport apr_pool_t +from apr_1.apr_hash cimport apr_hash_t +from apr_1.apr_time cimport apr_time_t + +include "_svn_api_ver.pxi" + +cdef extern from "svn_types.h" nogil: + ctypedef int svn_boolean_t + enum: TRUE + enum: FALSE + ctypedef struct svn_error_t: + apr_status_t apr_err + const char *message + svn_error_t *child + apr_pool_t *pool + const char *file + long line + ctypedef struct svn_version_t: + pass + + IF SVN_API_VER >= (1, 8): + ctypedef enum svn_node_kind_t: + svn_node_none, svn_node_file, svn_node_dir, + svn_node_unknown, svn_node_symlink + ELSE: + ctypedef enum svn_node_kind_t: + svn_node_none, svn_node_file, svn_node_dir, + svn_node_unknown + IF SVN_API_VER >= (1, 6): + const char * svn_node_kind_to_word(svn_node_kind_t kind) + svn_node_kind_t svn_node_kind_from_word(const char *word) + IF SVN_API_VER >= (1, 7): + ctypedef enum svn_tristate_t: + svn_tristate_false =2, svn_tristate_true, svn_tristate_unknown + const char * svn_tristate__to_word(svn_tristate_t tristate) + svn_tristate_t svn_tristate__from_word(const char *word) + ctypedef long int svn_revnum_t + enum: SVN_INVALID_REVNUM + enum: SVN_IGNORED_REVNUM + IF SVN_API_VER >= (1, 5): + svn_error_t * svn_revnum_parse(svn_revnum_t *rev, + const char *str, + const char **endptr) + ctypedef apr_int64_t svn_filesize_t + enum: SVN_INVALID_FILESIZE + ctypedef enum svn_recurse_kind: + svn_nonrecursive = 1, svn_recursive + IF SVN_API_VER >= (1, 5): + ctypedef enum svn_depth_t: + svn_depth_unknown = -2 + svn_depth_exclude = -1 + svn_depth_empty = 0 + svn_depth_files = 1 + svn_depth_immediates = 2 + svn_depth_infinity = 3 + const char * svn_depth_to_word(svn_depth_t depth) + svn_depth_t svn_depth_from_word(const char *word) + IF SVN_API_VER >= (1, 3): + ctypedef struct svn_dirent_t: + svn_node_kind_t kind + svn_filesize_t size + svn_boolean_t has_props + svn_revnum_t created_rev + apr_time_t time + const char *last_author + enum: SVN_DIRENT_KIND + enum: SVN_DIRENT_SIZE + enum: SVN_DIRENT_HAS_PROPS + enum: SVN_DIRENT_CREATED_REV + enum: SVN_DIRENT_TIME + enum: SVN_DIRENT_LAST_AUTHOR + enum: SVN_DIRENT_ALL + enum: SVN_STREAM_CHUNK_SIZE + ctypedef struct svn_log_changed_path_t: + char action + const char * copyfrom_path + svn_revnum_t copyfrom_rev + IF SVN_API_VER >= (1, 7): + ctypedef struct svn_log_changed_path2_t: + char action + const char * copyfrom_path + svn_revnum_t copyfrom_rev + svn_node_kind_t node_kind + svn_tristate_t text_modified + svn_tristate_t props_modified + ELIF SVN_API_VER >= (1, 6): + ctypedef struct svn_log_changed_path2_t: + char action + const char * copy_from_path + svn_revnum_t copyfrom_rev + svn_node_kind_t node_kind + IF SVN_API_VER >= (1, 7): + ctypedef struct svn_log_entry_t: + apr_hash_t * changed_paths + svn_revnum_t revision + apr_hash_t * revprops + svn_boolean_t has_children + apr_hash_t * changed_paths2 + svn_boolean_t non_inheritable + svn_boolean_t subtractive_merge + ELIF SVN_API_VER >= (1, 6): + ctypedef struct svn_log_entry_t: + apr_hash_t * changed_paths + svn_revnum_t revision + apr_hash_t * revprops + svn_boolean_t has_children + apr_hash_t * changed_paths2 + ELIF SVN_API_VER >= (1, 5): + ctypedef struct svn_log_entry_t: + apr_hash_t * changed_paths + svn_revnum_t revision + apr_hash_t * revprops + svn_boolean_t has_children + IF SVN_API_VER >= (1, 5): + ctypedef svn_error_t * (* svn_log_entry_receiver_t)( + void * baton, svn_log_entry_t * log_entry, apr_pool_t * pool) + ctypedef svn_error_t * (* svn_log_message_receiver_t)( + void * baton, apr_hash_t * changed_paths, svn_revnum_t revision, + const char * author, const char * date, const char * message, + apr_pool_t * pool) + + ctypedef svn_error_t * (* svn_cancel_func_t)(void * cancel_baton) + IF SVN_API_VER >= (1, 2): + ctypedef struct svn_lock_t: + const char *path + const char *token + const char *owner + const char *comment + svn_boolean_t is_dav_comment + apr_time_t creation_date + apr_time_t expiration_date diff --git a/src/lib/cython/capi/subversion_1/svn_version.pxd b/src/lib/cython/capi/subversion_1/svn_version.pxd new file mode 100644 index 00000000..434f4648 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_version.pxd @@ -0,0 +1,4 @@ +cdef extern from "svn_version.h" nogil: + enum: SVN_VER_MAJOR + enum: SVN_VER_MINOR + enum: SVN_VER_PATCH diff --git a/src/lib/cython/capi/subversion_1/svn_wc.pxd b/src/lib/cython/capi/subversion_1/svn_wc.pxd new file mode 100644 index 00000000..ca28c3c0 --- /dev/null +++ b/src/lib/cython/capi/subversion_1/svn_wc.pxd @@ -0,0 +1,40 @@ +include "_svn_api_ver.pxi" +from apr_1.apr cimport apr_int64_t, apr_uint32_t +from apr_1.apr_pools cimport apr_pool_t +from apr_1.apr_tables cimport apr_array_header_t +from apr_1.apr_time cimport apr_time_t +from subversion_1.svn_types cimport * +from subversion_1.svn_checksum cimport svn_checksum_t + +cdef extern from "svn_wc.h" nogil: + ctypedef enum svn_wc_schedule_t: + svn_wc_schedule_normal + svn_wc_schedule_add + svn_wc_schedule_delete + svn_wc_schedule_replace + IF SVN_API_VER >= (1, 8): + ctypedef struct svn_wc_info_t: + svn_wc_schedule_t schedule + const char * copyfrom_url + svn_revnum_t copyfrom_rev + const svn_checksum_t *checksum + const char * changelist + svn_depth_t depth + svn_filesize_t recorded_size + apr_time_t recorded_time + const apr_array_header_t * conflicts + const char * wcroot_abspath + const char * moved_from_abspath + const char * moved_to_abspath + ELSE: + ctypedef struct svn_wc_info_t: + svn_wc_schedule_t schedule + const char * copyfrom_url + svn_revnum_t copyfrom_rev + const svn_checksum_t *checksum + const char * changelist + svn_depth_t depth + svn_filesize_t recorded_size + apr_time_t recorded_time + const apr_array_header_t * conflicts + const char * wcroot_abspath diff --git a/src/lib/setup.cfg b/src/lib/setup.cfg new file mode 100644 index 00000000..04491a1d --- /dev/null +++ b/src/lib/setup.cfg @@ -0,0 +1,2 @@ +[install] +install-lib=../../lib diff --git a/src/lib/setup.py b/src/lib/setup.py new file mode 100644 index 00000000..eaebafb5 --- /dev/null +++ b/src/lib/setup.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python +# $yfId$ + +import sys +import os +import os.path +import shutil +import platform +import re +import subprocess +from distutils.core import setup +from Cython.Distutils.extension import Extension +from Cython.Distutils import build_ext +from distutils import log +from distutils.command.build import build as _build +from distutils.command.install import install as _install +from distutils.command.clean import clean as _clean +from distutils.cmd import Command + +build_base = os.path.abspath(os.path.dirname(sys.argv[0])) +cfg_py = os.path.join(build_base, 'cfg.py') + +# Get access to our library modules. +sys.path.insert(0, os.path.abspath( + os.path.join(build_base, '../../lib'))) + +# make sure import config module from build_base +sys.path.insert(0, build_base) + +try: + from cfg import apr_include_dir, svn_include_dir, apr_lib_dir,\ + svn_lib_dir, include_dirs, library_dirs + config_done = True +except: + apr_include_dir = None + svn_include_dir = None + apr_lib_dir = None + svn_lib_dir = None + include_dirs = [] + library_dirs = [] + config_done = False + + +def create_config_files(params=None): + # Fix me: library check may work only in Unix like platforms.... + def check_dir(dir, path_parts, exts): + for path in map((lambda x:os.path.join(dir, *x)), path_parts): + for ext in exts: + if os.path.isfile(path + ext): + return os.path.dirname(path) + return None + + def check_apr_include(dir): + return check_dir(dir, [['apr'], ['apr-1','apr']], ['.h']) + + def check_svn_include(dir): + return check_dir(dir, + [['svn_version'], ['subversion-1','svn_version']], + ['.h']) + + def check_apr_lib(dir): + return check_dir(dir, + [['libapr-1'], ['apr-1','libapr-1']], + ['.a','.la']) + + def check_svn_lib(dir): + return check_dir(dir, + [['libsvn_subr-1'], ['subversion-1','libsvn_subr-1']], + ['.a', '.la']) + + + include_path_candidate = ['/usr/include', '/usr/local/include'] + if ( os.path.lexists('/usr/lib64') + and re.match('.*64.*', platform.machine())): + lib_path_candidate = ['/usr/lib64', '/usr/local/lib64', + '/usr/lib', '/usr/local/lib'] + elif ( os.path.lexists('/usr/lib32') + and re.match('.*32.*', platform.machine())): + lib_path_candidate = ['/usr/lib32', '/usr/local/lib32', + '/usr/lib', '/usr/local/lib'] + else: + lib_path_candidate = ['/usr/lib', '/usr/local/lib'] + + if params and params.apr_include: + apr_include_path = check_apr_include(self.apr_include) + else: + for dir in include_path_candidate: + apr_include_dir = check_apr_include(dir) + if apr_include_dir: + break + if not apr_include_dir: + log.warn( +"""cannot determine APR include path. please (re)run 'python setup.py config' +with --apr-include= option +""") + sys.exit(1) + if params and params.svn_include: + svn_include_dir = check_svn_include(self.svn_include) + else: + for dir in include_path_candidate: + svn_include_dir = check_svn_include(dir) + if svn_include_dir: + break + if not svn_include_dir: + log.warn( +"""cannot determine Subversion include path. please (re)run +'python setup.py config' with --svn-include= option +""") + sys.exit(1) + if apr_include_dir == svn_include_dir: + include_dirs = [apr_include_dir] + else: + include_dirs = [apr_include_dir, svn_include_dir] + + if params and params.apr_lib: + apr_lib_dir = check_apr_lib(self.apr_lib) + else: + for dir in lib_path_candidate: + apr_lib_dir = check_apr_lib(dir) + if apr_lib_dir: + break + if not apr_lib_dir: + log.warn( +"""cannot determine APR library path. please (re)run 'python setup.py config' +with --apr-lib= option +""") + sys.exit(1) + if params and params.svn_lib: + svn_lib_dir = check_svn_lib(self.svn_lib) + else: + for dir in lib_path_candidate: + svn_lib_dir = check_svn_lib(dir) + if svn_lib_dir: + break + if not svn_lib_dir: + log.warn( +"""cannot determine Subversion library path. please (re)run +'python setup.py config' with --svn-lib= option +""") + sys.exit(1) + if apr_lib_dir == svn_lib_dir: + library_dirs = [apr_lib_dir] + else: + library_dirs = [apr_lib_dir, svn_lib_dir] + + fp = open(cfg_py, "wt") + fp.write('apr_include_dir = "{0}"\n'.format(apr_include_dir)) + fp.write('svn_include_dir = "{0}"\n'.format(svn_include_dir)) + fp.write('apr_lib_dir = "{0}"\n'.format(apr_lib_dir)) + fp.write('svn_lib_dir = "{0}"\n'.format(svn_lib_dir)) + fp.write('include_dirs = {0}\n'.format(repr(include_dirs))) + fp.write('library_dirs = {0}\n'.format(repr(library_dirs))) + fp.close + + # build make_svn_api_version_pxi + proc = subprocess.Popen( + ['cc', '-I' + apr_include_dir, + '-I' + svn_include_dir, '-o', 'make_svn_api_version_pxi', + 'make_svn_api_version_pxi.c'], + cwd=os.path.join(build_base, 'vclib/altsvn'), + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) + output = proc.stdout.read() + proc.stdout.close() + ret = proc.poll() + if ret is None: + proc.terminate() + ret = proc.wait() + if ret: + log.warn("C compiler exit code with {0!s}.".format(ret)) + log.warn("its output:\n{0}".format(output)) + sys.exit(1) + + # generate _svn_api_ver.pxi + pxi_file=os.path.join(build_base, + 'cython/capi/subversion_1/_svn_api_ver.pxi') + if os.path.lexists(pxi_file): + os.remove(pxi_file) + pxi_file=os.path.join(build_base, 'vclib/altsvn/_svn_api_ver.pxi') + if os.path.lexists(pxi_file): + os.remove(pxi_file) + proc = subprocess.Popen( + [os.path.join(build_base, + 'vclib/altsvn/make_svn_api_version_pxi')], + cwd=os.path.join(build_base, 'cython/capi/subversion_1'), + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) + output = proc.stdout.read() + proc.stdout.close() + ret = proc.poll() + if ret is None: + proc.terminate() + ret = proc.wait() + if ret: + log.warn("C compiler exit code with {0!s}.".format(ret)) + log.warn("its output:\n{0}".format(output)) + sys.exit(1) + try: + os.symlink('../../cython/capi/subversion_1/_svn_api_ver.pxi', + os.path.join(build_base, 'vclib/altsvn/_svn_api_ver.pxi')) + except: + shutil.copy2(os.path.join(build_base, 'cython/capi/_svn_api_ver.pxi'), + os.path.join(build_base, 'vclib/altsvn/_svn_api_ver.pxi')) + return + +class build(_build): + sub_commands = [('pre_build', None)] + _build.sub_commands + +class pre_build(Command): + description = "run pre-build jobs" + user_options = [] + boolean_options = [] + help_options = [] + def initialize_options(self): + return + def finalize_options(self): + return + def run(self): + if not config_done: + self.warn( +"""fail to import module 'config': Please (re)run + + $ python setup.py config + +with appropriate options.""") + sys.exit(1) + # put target python version into pxi file + pxi_file=os.path.join(build_base, 'vclib/altsvn/_py_ver.pxi') + if os.path.lexists(pxi_file): + os.remove(pxi_file) + f = open(pxi_file, 'w') + f.write('DEF PY_VERSION = ' + str((sys.version_info[0], + sys.version_info[1], + sys.version_info[2])) + + '\n') + f.close() + + +cython_include_dir = os.path.join(build_base, 'cython', 'capi') + +class config(Command): + description = "configure build envirionment" + user_options = [ + ('apr-include=', None, + "specify C include header path for apr-1"), + ('apr-lib=', None, + "specify C library path for apr-1"), + ('svn-include=', None, + "specify C include header path for Subversion"), + ('svn-lib=', None, + "C library path for Subversion")] + boolean_options = [] + help_options = [] + def initialize_options(self): + self.apr_include=None + self.apr_lib=None + self.svn_include=None + self.svn_lib=None + def finalize_options(self): + return + def run(self): + create_config_files(self) + return + +class install(_install): + # skip all except install_lib + sub_commands = [('install_lib', lambda self:True)] + +class clean(_clean): + intermediates = ['vclib/altsvn/_py_ver.pxi', + 'vclib/altsvn/_svn.c', + 'vclib/altsvn/_svn_repos.c', + 'vclib/altsvn/_svn_ra.c', + 'vclib/altsvn/make_svn_api_version_pxi'] + all_targets = ['cfg.py', 'cfg.pyc', 'cfg.pyo', '__pycache__', + 'cython/capi/subversion_1/_svn_api_ver.pxi', + 'vclib/altsvn/_svn_api_ver.pxi', + '../../lib/cython_debug', + '../../lib/vclib/altsvn'] + def run(self): + _clean.run(self) + def do_remove(path): + if os.path.lexists(path): + if os.path.islink(path) or os.path.isfile(path): + log.info("removing '%s'", path) + if not self.dry_run: + os.remove(path) + else: + assert os.path.isdir(path) + log.info("removing directory '%s'", path) + if not self.dry_run: + shutil.rmtree(path) + else: + log.warn("'%s' does not exist -- can't clean it", path) + + for intf in self.intermediates: + do_remove(intf) + if self.all: + for path in self.all_targets: + do_remove(path) + +ext_modules = [ + Extension('vclib.altsvn._svn', + ['vclib/altsvn/_svn.pyx'], + cython_include_dirs=[cython_include_dir], + cython_gdb=True, + # Whmm.. compiler specific option ... + #extra_compile_args=["-Wno-deprecated-declarations"], + include_dirs=include_dirs, + library_dirs=library_dirs, + libraries=["apr-1", "svn_subr-1", "svn_client-1"]), + Extension('vclib.altsvn._svn_repos', + ['vclib/altsvn/_svn_repos.pyx'], + cython_include_dirs=[cython_include_dir], + cython_gdb=True, + # Whmm.. compiler specific option ... + #extra_compile_args=["-Wno-deprecated-declarations"], + include_dirs=include_dirs, + library_dirs=library_dirs, + libraries=["apr-1", "svn_subr-1", "svn_fs-1", "svn_repos-1"]), + Extension('vclib.altsvn._svn_ra', + ['vclib/altsvn/_svn_ra.pyx'], + cython_include_dirs=[cython_include_dir], + cython_gdb=True, + # Whmm.. compiler specific option ... + #extra_compile_args=["-Wno-deprecated-declarations"], + include_dirs=include_dirs, + library_dirs=library_dirs, + libraries=["apr-1", "svn_subr-1", "svn_ra-1", "svn_client-1"]), +] + +setup(name='vclib.altsvn', + version='0.01.0', + description= 'alternative implementation of vclib.svn, ' + 'impremented with Cython', + author='Yasuhito FUTATSUKI', + author_email='futatuki@yf.bsdclub.org', + license="BSD 2 clause, Apache License Version 2", + packages = ['vclib.altsvn'], + ext_modules = ext_modules, + cmdclass = {'pre_build' : pre_build, + 'build' : build, + 'build_ext' : build_ext, + 'install' : install, + 'clean' : clean, + 'config' : config} +) diff --git a/src/lib/vclib/__init__.py b/src/lib/vclib/__init__.py new file mode 100644 index 00000000..8e5e7d9c --- /dev/null +++ b/src/lib/vclib/__init__.py @@ -0,0 +1,463 @@ +# -*-python-*- +# +# Copyright (C) 1999-2018 The ViewCVS Group. All Rights Reserved. +# +# By using this file, you agree to the terms and conditions set forth in +# the LICENSE.html file which can be found at the top level of the ViewVC +# distribution or at http://viewvc.org/license-1.html. +# +# For more information, visit http://viewvc.org/ +# +# ----------------------------------------------------------------------- + +"""Version Control lib is an abstract API to access versioning systems +such as CVS. +""" + +import sys +import io + + +# item types returned by Repository.itemtype(). +FILE = 'FILE' +DIR = 'DIR' + +# diff types recognized by Repository.rawdiff(). +UNIFIED = 1 +CONTEXT = 2 +SIDE_BY_SIDE = 3 + +# root types returned by Repository.roottype(). +CVS = 'cvs' +SVN = 'svn' + +# action kinds found in ChangedPath.action +ADDED = 'added' +DELETED = 'deleted' +REPLACED = 'replaced' +MODIFIED = 'modified' + +# log sort keys +SORTBY_DEFAULT = 0 # default/no sorting +SORTBY_DATE = 1 # sorted by date, youngest first +SORTBY_REV = 2 # sorted by revision, youngest first + + +# ====================================================================== +# +class Repository: + """Abstract class representing a repository.""" + + def rootname(self): + """Return the name of this repository.""" + + def roottype(self): + """Return the type of this repository (vclib.CVS, vclib.SVN, ...).""" + + def rootpath(self): + """Return the location of this repository.""" + + def authorizer(self): + """Return the vcauth.Authorizer object associated with this + repository, or None if no such association has been made.""" + + def open(self): + """Open a connection to the repository.""" + + 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, options): + """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 + number in canonical form. + + 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 file to check out + + options is a dictionary of implementation specific options + """ + + def listdir(self, path_parts, rev, options): + """Return list of files in a directory + + The result is a list of DirEntry objects + + 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, rev, entries, options): + """Augment directory entries with log information + + New properties will be set on all of the DirEntry objects in the entries + list. At the very least, a "rev" property will be set to a revision + number or None if the entry doesn't have a number. Other properties that + may be set include "date", "author", "log", "size", and "lockinfo". + + 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 + + options is a dictionary of implementation specific options + """ + + def itemlog(self, path_parts, rev, sortby, first, limit, options): + """Retrieve an item's log information + + The result is a list of Revision objects + + 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 return information about + + sortby indicates the way in which the returned list should be + sorted (SORTBY_DEFAULT, SORTBY_DATE, SORTBY_REV) + + first is the 0-based index of the first Revision returned (after + sorting, if any, has occured) + + limit is the maximum number of returned Revisions, or 0 to return + all available data + + options is a dictionary of implementation specific options + """ + + def itemprops(self, path_parts, rev): + """Return a dictionary mapping property names to property values + for properties stored on an item. + + 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 return information about. + """ + + def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}): + """Return a diff (in GNU diff format) of two file revisions + + type is the requested diff type (UNIFIED, CONTEXT, etc) + + options is a dictionary that can contain the following options plus + implementation-specific options + + context - integer, number of context lines to include + funout - boolean, include C function names + ignore_white - boolean, ignore whitespace + + Return value is a python file object + """ + + def annotate(self, path_parts, rev, include_text=False): + """Return a list of Annotation object, sorted by their + "line_number" components, which describe the lines of given + version of a file. + + The file 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 return information about. + + If include_text is true, populate the Annotation objects' "text" + members with the corresponding line of file content; otherwise, + leave that member set to None.""" + + def revinfo(self, rev): + """Return information about a global revision + + rev is the revision of the item to return information about + + Return value is a 5-tuple containing: the date, author, log + message, a list of ChangedPath items representing paths changed, + and a dictionary mapping property names to property values for + properties stored on an item. + + Raise vclib.UnsupportedFeature if the version control system + doesn't support a global revision concept. + """ + + def isexecutable(self, path_parts, rev): + """Return true iff a given revision of a versioned file is to be + considered an executable program or script. + + 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 return information about + """ + + def filesize(self, path_parts, rev): + """Return the size of a versioned file's contents if it can be + obtained without a brute force measurement; -1 otherwise. + + NOTE: Callers that require a filesize answer when this function + returns -1 may obtain it by measuring the data returned via + openfile(). + + 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 return information about + """ + + +# ====================================================================== +class DirEntry: + """Instances represent items in a directory listing""" + + def __init__(self, name, kind, errors=[]): + """Create a new DirEntry() item: + NAME: The name of the directory entry + KIND: The path kind of the entry (vclib.DIR, vclib.FILE) + ERRORS: A list of error strings representing problems encountered + while determining the other info about this entry + """ + self.name = name + self.kind = kind + self.errors = errors + +class Revision: + """Instances holds information about revisions of versioned resources""" + + def __init__(self, number, string, date, author, changed, log, size, lockinfo): + """Create a new Revision() item: + NUMBER: Revision in an integer-based, sortable format + STRING: Revision as a string + DATE: Seconds since Epoch (GMT) that this revision was created + AUTHOR: Author of the revision + CHANGED: Lines-changed (contextual diff) information + LOG: Log message associated with the creation of this revision + SIZE: Size (in bytes) of this revision's fulltext (files only) + LOCKINFO: Information about locks held on this revision + """ + self.number = number + self.string = string + self.date = date + self.author = author + self.changed = changed + self.log = log + self.size = size + self.lockinfo = lockinfo + + if sys.version_info[0] >= 3: + def __lt__(self, other): + return (self.number < other.number) + else: + def __cmp__(self, other): + return cmp(self.number, other.number) + +class Annotation: + """Instances represent per-line file annotation information""" + + def __init__(self, text, line_number, rev, prev_rev, author, date): + """Create a new Annotation() item: + TEXT: Raw text of a line of file contents + LINE_NUMBER: Line number on which the line is found + REV: Revision in which the line was last modified + PREV_REV: Revision prior to 'rev' + AUTHOR: Author who last modified the line + DATE: Date on which the line was last modified, in seconds since + the epoch, GMT + """ + self.text = text + self.line_number = line_number + self.rev = rev + self.prev_rev = prev_rev + self.author = author + self.date = date + +class ChangedPath: + """Instances represent changes to paths""" + + def __init__(self, path_parts, rev, pathtype, base_path_parts, + base_rev, action, copied, text_changed, props_changed): + """Create a new ChangedPath() item: + PATH_PARTS: Path that was changed + REV: Revision represented by this change + PATHTYPE: Type of this path (vclib.DIR, vclib.FILE, ...) + BASE_PATH_PARTS: Previous path for this changed item + BASE_REV: Previous revision for this changed item + ACTION: Kind of change (vclib.ADDED, vclib.DELETED, ...) + COPIED: Boolean -- was this path copied from elsewhere? + TEXT_CHANGED: Boolean -- did the file's text change? + PROPS_CHANGED: Boolean -- did the item's metadata change? + """ + self.path_parts = path_parts + self.rev = rev + self.pathtype = pathtype + self.base_path_parts = base_path_parts + self.base_rev = base_rev + self.action = action + self.copied = copied + self.text_changed = text_changed + self.props_changed = props_changed + + +# ====================================================================== + +class Error(Exception): + pass + +class ReposNotFound(Error): + pass + +class UnsupportedFeature(Error): + pass + +class ItemNotFound(Error): + def __init__(self, path): + # use '/' rather than os.sep because this is for user consumption, and + # it was defined using URL separators + if isinstance(path, tuple) or isinstance(path, list): + path = '/'.join(path) + Error.__init__(self, path) + +class InvalidRevision(Error): + def __init__(self, revision=None): + if revision is None: + Error.__init__(self, "Invalid revision") + else: + Error.__init__(self, "Invalid revision " + str(revision)) + +class NonTextualFileContents(Error): + pass + +# ====================================================================== +# Implementation code used by multiple vclib modules + +import subprocess +import sys +import os +import time + +def _diff_args(type, options): + """generate argument list to pass to diff or rcsdiff""" + args = [] + if type == CONTEXT: + if 'context' in options: + if options['context'] is None: + args.append('--context=-1') + else: + args.append('--context=%i' % options['context']) + else: + args.append('-c') + elif type == UNIFIED: + if 'context' in options: + if options['context'] is None: + args.append('--unified=-1') + else: + args.append('--unified=%i' % options['context']) + else: + args.append('-u') + elif type == SIDE_BY_SIDE: + args.append('--side-by-side') + args.append('--width=164') + else: + raise NotImplementedError + + if options.get('funout', 0): + args.append('-p') + + if options.get('ignore_white', 0): + args.append('-w') + + return args + +class _diff_fp: + """File object reading a diff between temporary files, cleaning up + on close""" + + def __init__(self, temp1, temp2, info1=None, info2=None, diff_cmd='diff', diff_opts=[]): + self.temp1 = temp1 + self.temp2 = temp2 + args = diff_opts[:] + args.insert(0, diff_cmd) + if info1 and info2: + args.extend(["-L", self._label(info1), "-L", self._label(info2)]) + args.extend([temp1, temp2]) + self.proc = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=-1, + close_fds=(sys.platform != "win32")) + if ( not isinstance(self.proc.stdout, io.TextIOBase) + and isinstance(self.proc.stdout, io.BufferedIOBase)): + self.fp = io.TextIOWrapper(self.proc.stdout, + encoding='utf-8', errors='surrogateescape') + else: + self.fp = self.proc.stdout + + def read(self, bytes): + return self.fp.read(bytes) + + def readline(self): + return self.fp.readline() + + def close(self): + try: + if self.proc: + self.fp.close() + ret = self.proc.poll() + if ret is None: + # child process seems to be still running... + self.proc.terminate() + self.proc = None + finally: + try: + if self.temp1: + os.remove(self.temp1) + self.temp1 = None + finally: + if self.temp2: + os.remove(self.temp2) + self.temp2 = None + + def __del__(self): + self.close() + + def _label(self, info): + path, date, rev = info + date = date and time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date)) + return "%s\t%s\t%s" % (path, date, rev) + + +def check_root_access(repos): + """Return 1 iff the associated username is permitted to read REPOS, + as determined by consulting REPOS's Authorizer object (if any).""" + + auth = repos.authorizer() + if not auth: + return 1 + return auth.check_root_access(repos.rootname()) + +def check_path_access(repos, path_parts, pathtype=None, rev=None): + """Return 1 iff the associated username is permitted to read + revision REV of the path PATH_PARTS (of type PATHTYPE) in repository + REPOS, as determined by consulting REPOS's Authorizer object (if any).""" + + auth = repos.authorizer() + if not auth: + return 1 + if not pathtype: + pathtype = repos.itemtype(path_parts, rev) + return auth.check_path_access(repos.rootname(), path_parts, pathtype, rev) + diff --git a/src/lib/vclib/altsvn/__init__.py b/src/lib/vclib/altsvn/__init__.py new file mode 100644 index 00000000..38385df4 --- /dev/null +++ b/src/lib/vclib/altsvn/__init__.py @@ -0,0 +1,157 @@ +# -*-python-*- +# +# Copyright (C) 1999-2018 The ViewCVS Group. All Rights Reserved. +# +# By using this file, you agree to the terms and conditions set forth in +# the LICENSE.html file which can be found at the top level of the ViewVC +# distribution or at http://viewvc.org/license-1.html. +# +# For more information, visit http://viewvc.org/ +# +# ----------------------------------------------------------------------- + +"Version Control lib driver for Subversion repositories" + +import sys +import os.path +import re +import vclib +from ._svn import canonicalize_path as _canonicalize_path +from ._svn import canonicalize_rootpath as canonicalize_rootpath + +# Python 3: workaround for cmp() +if sys.version_info[0] >= 3: + def cmp(a, b): + return (a > b) - (a < b) + +def _path_parts(path): + return [pp for pp in path.split('/') if pp] + +def _cleanup_path(path): + """Return a cleaned-up Subversion filesystem path""" + return '/'.join([pp for pp in path.split('/') if pp]) + + +def _compare_paths(path1, path2): + path1_len = len (path1); + path2_len = len (path2); + min_len = min(path1_len, path2_len) + i = 0 + + # Are the paths exactly the same? + if path1 == path2: + return 0 + + # Skip past common prefix + while (i < min_len) and (path1[i] == path2[i]): + i = i + 1 + + # Children of paths are greater than their parents, but less than + # greater siblings of their parents + char1 = '\0' + char2 = '\0' + if (i < path1_len): + char1 = path1[i:i+1] + if (i < path2_len): + char2 = path2[i:i+1] + + if (char1 == '/') and (i == path2_len): + return 1 + if (char2 == '/') and (i == path1_len): + return -1 + if (i < path1_len) and (char1 == '/'): + return -1 + if (i < path2_len) and (char2 == '/'): + return 1 + + # Common prefix was skipped above, next character is compared to + # determine order + return cmp(char1, char2) + + +# Given a dictionary REVPROPS of revision properties, pull special +# ones out of them and return a 4-tuple containing the log message, +# the author, the date (converted from the date string property), and +# a dictionary of any/all other revprops. +def _split_revprops(revprops, scratch_pool=None): + if not revprops: + return None, None, None, {} + special_props = [] + for prop in _svn.SVN_PROP_REVISION_LOG, \ + _svn.SVN_PROP_REVISION_AUTHOR, \ + _svn.SVN_PROP_REVISION_DATE: + if prop in revprops: + special_props.append(revprops[prop]) + del(revprops[prop]) + else: + special_props.append(None) + msg, author, datestr = tuple(special_props) + date = _svn.datestr_to_date(datestr, scratch_pool) + return msg, author, date, revprops + + +_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://') + +def expand_root_parent(parent_path): + roots = {} + if re.search(_re_url, parent_path): + pass + else: + # Any subdirectories of PARENT_PATH which themselves have a child + # "format" are returned as roots. + assert os.path.isabs(parent_path) + subpaths = os.listdir(parent_path) + for rootname in subpaths: + rootpath = os.path.join(parent_path, rootname) + if os.path.exists(os.path.join(rootpath, "format")): + roots[rootname] = canonicalize_rootpath(rootpath) + return roots + + +def find_root_in_parent(parent_path, rootname): + """Search PARENT_PATH for a root named ROOTNAME, returning the + canonicalized ROOTPATH of the root if found; return None if no such + root is found.""" + + if not re.search(_re_url, parent_path): + assert os.path.isabs(parent_path) + rootpath = os.path.join(parent_path, rootname) + format_path = os.path.join(rootpath, "format") + if os.path.exists(format_path): + return canonicalize_rootpath(rootpath) + return None + + +class Revision(vclib.Revision): + "Hold state for each revision's log entry." + def __init__(self, rev, date, author, msg, size, lockinfo, + filename, copy_path, copy_rev): + vclib.Revision.__init__(self, rev, str(rev), date, author, None, + msg, size, lockinfo) + self.filename = filename + self.copy_path = copy_path + self.copy_rev = copy_rev + + +class SVNChangedPath(vclib.ChangedPath): + """Wrapper around vclib.ChangedPath which handles path splitting.""" + + def __init__(self, path, rev, pathtype, base_path, base_rev, + action, copied, text_changed, props_changed): + path_parts = _path_parts(path or '') + base_path_parts = _path_parts(base_path or '') + vclib.ChangedPath.__init__(self, path_parts, rev, pathtype, + base_path_parts, base_rev, action, + copied, text_changed, props_changed) + + +def SubversionRepository(name, rootpath, authorizer, utilities, config_dir): + rootpath = canonicalize_rootpath(rootpath) + if re.search(_re_url, rootpath): + from . import svn_ra + return svn_ra.RemoteSubversionRepository(name, rootpath, authorizer, + utilities, config_dir) + else: + from . import svn_repos + return svn_repos.LocalSubversionRepository(name, rootpath, authorizer, + utilities, config_dir) diff --git a/src/lib/vclib/altsvn/_svn.pxd b/src/lib/vclib/altsvn/_svn.pxd new file mode 100644 index 00000000..76030756 --- /dev/null +++ b/src/lib/vclib/altsvn/_svn.pxd @@ -0,0 +1,180 @@ +include "_py_ver.pxi" +include "_svn_api_ver.pxi" +cimport _svn_capi as _c_ + +cdef class Apr_Pool(object): + cdef _c_.apr_pool_t* _c_pool + cdef _c_.svn_boolean_t is_own + cdef readonly Apr_Pool _parent_pool + cdef Apr_Pool set_pool(Apr_Pool self, _c_.apr_pool_t * _c_pool) + cdef inline void * palloc(self, _c_.apr_size_t size) + +cpdef Apr_Pool _root_pool +cpdef Apr_Pool _scratch_pool + +cdef class Svn_error(object): + cdef _c_.svn_error_t * _c_error + cdef object bytes_msg + cdef seterror(self, _c_.svn_error_t * err) + cdef _c_.svn_error_t * geterror(self) + +cdef class svn_opt_revision_t(object): + cdef _c_.svn_opt_revision_t _c_opt_revision + cdef svn_opt_revision_t _c_set(self, _c_.svn_opt_revision_t _c_rev) + +cdef class svn_opt_revision_range_t(object): + cdef _c_.svn_opt_revision_range_t _c_range + +cdef class svn_stream_t(object): + cdef _c_.svn_stream_t * _c_ptr + cdef Apr_Pool pool + cdef svn_stream_t set_stream( + svn_stream_t self, _c_.svn_stream_t * stream, object pool) + +cdef class CbContainer(object): + cdef object fnobj + cdef object btn + cdef object pool + +cdef class TransPtr(object): + cdef object to_object(self) + cdef void * from_object(self, object obj) + cdef void set_ptr(self, void *_c_ptr) + cdef void ** ptr_ref(self) + +cdef class HashTrans(TransPtr): + cdef _c_.apr_hash_t * _c_hash + cdef TransPtr key_trans + cdef TransPtr val_trans + cdef Apr_Pool tmp_pool + cdef object to_object(self) + cdef void * from_object(self, object obj) + cdef void set_ptr(self, void *_c_ptr) + cdef void ** ptr_ref(self) + +ctypedef object (*ptr_to_pyobj_func_t)(void *_c_ptr, + _c_.apr_pool_t * _c_scratch_pool) + +cdef object hash_to_dict(_c_.apr_hash_t * _c_hash, + ptr_to_pyobj_func_t key_func, + ptr_to_pyobj_func_t val_func, + _c_.apr_pool_t * _c_scratch_pool) + +cdef class CStringTransBytes(TransPtr): + cdef char * _c_str + cdef object to_object(self) + cdef void set_ptr(self, void *_c_ptr) + cdef void ** ptr_ref(self) + +IF PY_VERSION >= (3, 0, 0): + cdef class CStringTransStr(TransPtr): + cdef char * _c_str + cdef object to_object(self) + cdef void set_ptr(self, void *_c_ptr) + cdef void ** ptr_ref(self) +ELSE: + cdef class CStringTransStr(CStringTransBytes): + pass + +cdef class SvnStringTransBytes(TransPtr): + cdef _c_.svn_string_t * _c_svn_str + cdef object to_object(self) + cdef void set_ptr(self, void *_c_ptr) + cdef void ** ptr_ref(self) + +IF PY_VERSION >= (3, 0, 0): + cdef class SvnStringTransStr(TransPtr): + cdef _c_.svn_string_t * _c_svn_str + cdef object to_object(self) + cdef void set_ptr(self, void *_c_ptr) + cdef void ** ptr_ref(self) +ELSE: + cdef class SvnStringTransStr(SvnStringTransBytes): + pass + +cdef class SvnBooleanTrans(TransPtr): + cdef _c_.svn_boolean_t _c_bool + cdef object to_object(self) + cdef void set_c_bool(self, _c_.svn_boolean_t _c_bool) + cdef void ** ptr_ref(self) + + +cdef _c_.apr_array_header_t * make_revnum_array( + object revisions, _c_.apr_pool_t * pool) except? NULL + + +cdef class SvnRevnumPtrTrans(TransPtr): + cdef _c_.svn_revnum_t * _c_revptr + cdef object to_object(self) + cdef void set_c_revptr(self, _c_.svn_revnum_t * _c_revptr) + cdef void ** ptr_ref(self) + + +cdef class _py_stream_baton(object): + cdef object baton + IF SVN_API_VER >= (1, 7): + # placeholder to hold marks for mark/seek operation + # we can use Python object allocation, so we use + # svn_stream_mark_t as integer key of marks, and hold actual mark + # as value of marks dict. + cdef dict marks + # next_mark == len(marks)+1 + cdef int next_mark + + +cdef class _py_io_stream_baton(_py_stream_baton): + cdef readonly object fo + cdef public object is_eof + + +cdef class _py_generic_stream_baton(_py_stream_baton): + cdef object read_fn + IF SVN_API_VER >= (1, 9): + cdef object read_full_fn + IF SVN_API_VER >= (1, 7): + cdef object skip_fn + cdef object write_fn + cdef object close_fn + IF SVN_API_VER >= (1, 7): + cdef object mark_fn + cdef object seek_fn + IF SVN_API_VER >= (1, 9): + cdef object data_available_fn + IF SVN_API_VER >= (1, 10): + cdef object readline_fn + + +cdef class CharPtrWriteBuffer: + cdef char * _c_buf + cdef Py_ssize_t len + cdef Py_ssize_t shape[1] + cdef Py_ssize_t strides[1] + cdef CharPtrWriteBuffer set_buffer( + CharPtrWriteBuffer self, char * _c_buf, Py_ssize_t len) + + +cdef class CharPtrReadBuffer: + cdef const char * _c_buf + cdef Py_ssize_t len + cdef Py_ssize_t shape[1] + cdef Py_ssize_t strides[1] + cdef CharPtrReadBuffer set_buffer( + CharPtrReadBuffer self, const char * _c_buf, Py_ssize_t len) + + +cdef class py_io_stream(svn_stream_t): + cdef _py_io_stream_baton baton + + +cdef class py_stream(svn_stream_t): + cdef _py_generic_stream_baton baton + + +cdef class svn_client_ctx_t(object): + cdef _c_.svn_client_ctx_t * _c_ctx + cdef Apr_Pool pool + cdef svn_client_ctx_t set_ctx(self, _c_.svn_client_ctx_t * ctx, pool) + + +cpdef svn_client_ctx_t setup_client_ctx( + object config_dir, object result_pool=?) diff --git a/src/lib/vclib/altsvn/_svn.pyx b/src/lib/vclib/altsvn/_svn.pyx new file mode 100644 index 00000000..f9ab1315 --- /dev/null +++ b/src/lib/vclib/altsvn/_svn.pyx @@ -0,0 +1,2413 @@ +include "_svn_api_ver.pxi" +include "_py_ver.pxi" +from libc.stdlib cimport atexit +from libc.stddef cimport size_t +from libc.stdint cimport int64_t +from libc.string cimport memcpy +from cpython cimport Py_buffer +from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free +cimport _svn_capi as _c_ + +import os +import os.path +import io +import errno + +IF PY_VERSION >= (3, 0, 0): + import sys + import codecs + +IF SVN_API_VER < (1, 7): + import urllib + +try: + PathLike = os.PathLike +except AttributeError: + class PathLike(object): + pass + +IF PY_VERSION >= (3, 0, 0): + # for compatibility between Python 2 and Python 3 + _default_encoding = sys.getdefaultencoding() + + def setdefaultencoding(enc): + codecs.lookup(enc) + _default_encoding = enc + return + + def _norm(s, encoding=_default_encoding, errors='surrogateescape'): + return (s.decode(encoding, errors) + if not isinstance(s, str) and isinstance(s, bytes) else s) +ELSE: + def setdefaultencoding(enc): + return + def _norm(s, encoding=None, errors=None): + return s + +class General(Exception): pass +class InitError(General): pass +class MemoryError(General): pass +class PoolError(General): pass +# for internal use +class NotImplemented(General): pass + +# from "apr_pools.h" representation of apr_pool_t +cdef class Apr_Pool(object): +# cdef _c_.apr_pool_t* _c_pool + def __cinit__(self, Apr_Pool pool=None): + self._c_pool = NULL + self.is_own = _c_.FALSE + self._parent_pool = None + def __init__(self, Apr_Pool pool=None): + cdef _c_.apr_status_t ast + global _root_pool + if pool is None: + self._parent_pool = _root_pool + ast = _c_.apr_pool_create(&(self._c_pool), _root_pool._c_pool) + else: + self._parent_pool = pool + ast = _c_.apr_pool_create(&(self._c_pool), pool._c_pool) + if ast: + raise PoolError() + self.is_own = _c_.TRUE + def clear(self): + if self._c_pool is not NULL: + _c_.apr_pool_clear(self._c_pool) + def destroy(self): + # do not try to destroy the pool. this will cause segmentation fault. + if self.is_own != _c_.FALSE and self._c_pool is not NULL: + self.is_own = _c_.FALSE + _c_.apr_pool_destroy(self._c_pool) + self._c_pool = NULL + cdef Apr_Pool set_pool(Apr_Pool self, _c_.apr_pool_t * _c_pool): + assert self._c_pool is NULL + self.is_own = _c_.FALSE + self._c_pool = _c_pool + return self + cdef inline void * palloc(self, _c_.apr_size_t size): + return _c_.apr_palloc(self._c_pool, size) + def __dealloc__(self): + if self.is_own != _c_.FALSE and self._c_pool is not NULL: + self.is_own = _c_.FALSE + _c_.apr_pool_destroy(self._c_pool) + self._c_pool = NULL + self._parent_pool = None + +cpdef Apr_Pool _root_pool +cpdef Apr_Pool _scratch_pool + +def _initialize(): + cdef void* errstrbuf + cdef _c_.apr_status_t ast + cdef int nelm = 1024 + cdef size_t bufsize + cdef crv + global _root_pool, _scratch_pool + ast = _c_.apr_initialize() + if ast: + bufsize = nelm * sizeof(char) + errstrbuf = PyMem_Malloc(nelm) + if not errstrbuf: + raise MemoryError() + estr = _c_.apr_strerror(ast, errstrbuf, bufsize) + PyMem_Free(errstrbuf) + raise InitError(estr) + else: + if 0 != atexit(_c_.apr_terminate): + _c_.apr_terminate() + raise MemoryError() + # setup _root_pool and _scratch_pool + _root_pool = Apr_Pool.__new__(Apr_Pool, None) + _root_pool._parent_pool = None + _c_.apr_pool_create(&(_root_pool._c_pool), NULL) + assert _root_pool._c_pool is not NULL + _root_pool.is_own = _c_.TRUE + _scratch_pool = Apr_Pool(_root_pool) + return + +_initialize() +del _initialize + +# from "svn_error_codes.h" +SVN_NO_ERROR = _c_.SVN_NO_ERROR +SVN_ERR_FS_NOT_FOUND = _c_.SVN_ERR_FS_NOT_FOUND +SVN_ERR_CLIENT_IS_BINARY_FILE = _c_.SVN_ERR_CLIENT_IS_BINARY_FILE +IF SVN_API_VER >= (1, 5): + SVN_ERR_CEASE_INVOCATION = _c_.SVN_ERR_CEASE_INVOCATION +ELSE: + SVN_ERR_CEASE_INVOCATION = _c_.SVN_ERR_CANCELLED + +# from "svn_types.h" representation of svn_error_t +cdef class Svn_error(object): +# cdef _c_.svn_error_t * _c_error +# cdef object bytes_msg + def __cinit__(self, msg=None, stat=None): + self._c_error = NULL + def __init__(self, msg=None, stat=None): + cdef _c_.apr_status_t ast + cdef const char * _c_msg + if stat: + ast = stat + if msg: + IF PY_VERSION < (3, 0, 0): + self.bytes_msg = str(msg) + ELSE: + self.bytes_msg = str(msg).encode('utf-8') + _c_msg = self.bytes_msg + else: + _c_msg = NULL + self._c_error = _c_.svn_error_create(stat, NULL, _c_msg) + cdef seterror(self, _c_.svn_error_t * err): + if err is not NULL: + if self._c_error is NULL: + self._c_error = err + elif err is not NULL: + _c_.svn_error_compose(self._c_error, err) + return self + cdef _c_.svn_error_t * geterror(self): + return self._c_error + def __str__(self): + cdef object estr + cdef _c_.svn_error_t * eptr + cdef char * msgbuf + IF SVN_API_VER >= (1, 4): + msgbuf = PyMem_Malloc(512) + estr = _c_.svn_err_best_message( + self._c_error, msgbuf, <_c_.apr_size_t>512) + PyMem_Free(msgbuf) + ELSE: + if self._c_error is NULL: + estr = b'' + else: + eptr = self._c_error + if eptr.message is not NULL: + estr = eptr.message + else: + estr = b'' + eptr = eptr.child + while eptr is not NULL: + if eptr.message is not NULL: + estr = estr + b'\n' + eptr.message + eptr = eptr.child + + IF PY_VERSION >= (3, 0, 0): + estr = estr.decode('utf-8', 'surrogateescape') + return estr + def __dealloc__(self): + # svn_error_clear allows to clear NULL + _c_.svn_error_clear(self._c_error) + + +# Svn_error as exception +class SVNerr(General): + def __init__(self, msg=None, stat=None): + if isinstance(msg, Svn_error): + self.svnerr = msg + else: + self.svnerr = Svn_error(msg, stat) + def __str__(self): + return str(self.svnerr) + def __repr__(self): + emsg = str(self.svnerr) + ecode = self.getcode() + if emsg: + return ''.format(ecode, emsg) + else: + return ''.format(ecode) + def get_code(self): + if (self.svnerr)._c_error is NULL: + return 0 + else: + return (self.svnerr)._c_error.apr_err + def get_code_list(self): + cdef _c_.svn_error_t * eptr + eptr = (self.svnerr)._c_error + r = [] + while eptr is not NULL: + r.append(eptr.apr_err) + eptr = eptr.child + return r + +# from "svn_types.h" +SVN_INVALID_REVNUM = _c_.SVN_INVALID_REVNUM +SVN_IGNORED_REVNUM = _c_.SVN_IGNORED_REVNUM +SVN_STREAM_CHUNK_SIZE = _c_.SVN_STREAM_CHUNK_SIZE + +# from "svn_types.h" svn_tristate_t +svn_tristate_false = _c_.svn_tristate_false +svn_tristate_true = _c_.svn_tristate_true +svn_tristate_unknown = _c_.svn_tristate_unknown + +# from "svn_types.h" svn_node_kind_t +svn_node_none = _c_.svn_node_none +svn_node_file = _c_.svn_node_file +svn_node_dir = _c_.svn_node_dir +svn_node_unknown = _c_.svn_node_unknown +IF SVN_API_VER >= (1, 8): + svn_node_symlink = _c_.svn_node_symlink +IF SVN_API_VER >= (1, 6): + def svn_node_kind_to_word(_c_.svn_node_kind_t kind): + return _c_.svn_node_kind_to_word(kind) + def svn_node_kind_from_word(const char * word): + return _c_.svn_node_kind_from_word(word) + +# from "svn_props.h" +IF PY_VERSION < (3, 0, 0): + SVN_PROP_REVISION_LOG = _c_.SVN_PROP_REVISION_LOG + SVN_PROP_REVISION_AUTHOR = _c_.SVN_PROP_REVISION_AUTHOR + SVN_PROP_REVISION_DATE = _c_.SVN_PROP_REVISION_DATE + SVN_PROP_EXECUTABLE = _c_.SVN_PROP_EXECUTABLE + SVN_PROP_SPECIAL = _c_.SVN_PROP_SPECIAL +ELSE: + SVN_PROP_REVISION_LOG = ( + (_c_.SVN_PROP_REVISION_LOG)).decode('utf-8') + SVN_PROP_REVISION_AUTHOR = ( + (_c_.SVN_PROP_REVISION_AUTHOR)).decode('utf-8') + SVN_PROP_REVISION_DATE = ( + (_c_.SVN_PROP_REVISION_DATE)).decode('utf-8') + SVN_PROP_EXECUTABLE = ( + (_c_.SVN_PROP_EXECUTABLE)).decode('utf-8') + SVN_PROP_SPECIAL = ( + (_c_.SVN_PROP_SPECIAL)).decode('utf-8') + +# from "svn_version.h" +SVN_VER_MAJOR = _c_.SVN_VER_MAJOR +SVN_VER_MINOR = _c_.SVN_VER_MINOR +SVN_VER_PATCH = _c_.SVN_VER_PATCH + +# from "svn_opt.h" +svn_opt_revision_unspecified = _c_.svn_opt_revision_unspecified +svn_opt_revision_number = _c_.svn_opt_revision_number +svn_opt_revision_date = _c_.svn_opt_revision_date +svn_opt_revision_committed = _c_.svn_opt_revision_committed +svn_opt_revision_previous = _c_.svn_opt_revision_previous +svn_opt_revision_base = _c_.svn_opt_revision_base +svn_opt_revision_working = _c_.svn_opt_revision_working +svn_opt_revision_head = _c_.svn_opt_revision_head + +cdef class svn_opt_revision_t(object): +# cdef _c_.svn_opt_revision_t _c_opt_revision + def __cinit__(self, kind=_c_.svn_opt_revision_unspecified, value=0): + self.set(kind, value) + def set(self, kind, value=0): + cdef svn_opt_revision_t ref + if kind is None: + self._c_opt_revision.kind = _c_.svn_opt_revision_unspecified + self._c_opt_revision.value.number = 0 + elif isinstance(kind, svn_opt_revision_t): + ref = kind + self._c_opt_revision.kind = ref._c_opt_revision.kind + self._c_opt_revision.value.number = \ + ref._c_opt_revision.value.number + elif kind == _c_.svn_opt_revision_number: + self._c_opt_revision.kind = kind + self._c_opt_revision.value.number = value + elif kind == _c_.svn_opt_revision_date: + self._c_opt_revision.kind = kind + self._c_opt_revision.value.date = value + elif kind in [ + _c_.svn_opt_revision_unspecified, + _c_.svn_opt_revision_committed, + _c_.svn_opt_revision_previous, + _c_.svn_opt_revision_base, + _c_.svn_opt_revision_working, + _c_.svn_opt_revision_head]: + self._c_opt_revision.kind = kind + self._c_opt_revision.value.number = 0 + else: + raise ValueError('unknown svn_opt_revision_kind: ' + str(kind)) + return self + cdef svn_opt_revision_t _c_set(self, _c_.svn_opt_revision_t _c_rev): + if _c_rev.kind == _c_.svn_opt_revision_number: + self._c_opt_revision.kind = _c_rev.kind + self._c_opt_revision.value.number = _c_rev.value.number + elif _c_rev.kind == _c_.svn_opt_revision_date: + self._c_opt_revision.kind = _c_rev.kind + self._c_opt_revision.value.date = _c_rev.value.date + elif _c_rev.kind in [ + _c_.svn_opt_revision_unspecified, + _c_.svn_opt_revision_committed, + _c_.svn_opt_revision_previous, + _c_.svn_opt_revision_base, + _c_.svn_opt_revision_working, + _c_.svn_opt_revision_head]: + self._c_opt_revision.kind = _c_rev.kind + self._c_opt_revision.value.number = 0 + else: + raise ValueError('unknown svn_opt_revision_kind: ' + + str(_c_rev.kind)) + return self + @property + def kind(self): + return self._c_opt_revision.kind + @kind.setter + def kind(self, kind): + if kind == _c_.svn_opt_revision_number: + self._c_opt_revision.kind = kind + self._c_opt_revision.value.number = _c_.SVN_INVALID_REVNUM + elif kind == _c_.svn_opt_revision_date: + self._c_opt_revision.kind = kind + self._c_opt_revision.value.date = 0 + elif kind in [ + _c_.svn_opt_revision_unspecified, + _c_.svn_opt_revision_committed, + _c_.svn_opt_revision_previous, + _c_.svn_opt_revision_base, + _c_.svn_opt_revision_working, + _c_.svn_opt_revision_head]: + self._c_opt_revision.kind = kind + self._c_opt_revision.value.number = 0 + else: + raise ValueError('unknown svn_opt_revision_kind: ' + str(kind)) + @property + def value(self): + if self._c_opt_revision.kind == _c_.svn_opt_revision_number: + return self._c_opt_revision.value.number + elif self._c_opt_revision.kind == _c_.svn_opt_revision_date: + return self._c_opt_revision.value.date + elif self._c_opt_revision.kind in [ + _c_.svn_opt_revision_unspecified, + _c_.svn_opt_revision_committed, + _c_.svn_opt_revision_previous, + _c_.svn_opt_revision_base, + _c_.svn_opt_revision_working, + _c_.svn_opt_revision_head]: + # Though value has no mean, return it + return self._c_opt_revision.value.number + else: + # foolproof + raise ValueError('unknown svn_opt_revision_kind "' + + str(self._c_opt_revision.kind) + + '" has set') + @value.setter + def value(self, value): + if self._c_opt_revision.kind == _c_.svn_opt_revision_number: + self._c_opt_revision.value.number = value + elif self._c_opt_revision.kind == _c_.svn_opt_revision_date: + self._c_opt_revision.value.date = value + elif self._c_opt_revision.kind in [ + _c_.svn_opt_revision_unspecified, + _c_.svn_opt_revision_committed, + _c_.svn_opt_revision_previous, + _c_.svn_opt_revision_base, + _c_.svn_opt_revision_working, + _c_.svn_opt_revision_head]: + # Though value has no mean, set it + self._c_opt_revision.value.number = value + else: + # foolproof + raise ValueError('unknown svn_opt_revision_kind "' + + str(self._c_opt_revision.kind) + + '" has set') + + +# only for placeholder used by svn_client_log*() +cdef class svn_opt_revision_range_t(object): + def __cinit__(self, start, end, **m): + self._c_range.start.kind = _c_.svn_opt_revision_unspecified + self._c_range.start.value.number = 0 + self._c_range.end.kind = _c_.svn_opt_revision_unspecified + self._c_range.end.value.number = 0 + def __init__(self, start, end, **m): + memcpy(&(self._c_range.start), + &((start)._c_opt_revision), + sizeof(self._c_range.start)) + memcpy(&(self._c_range.end), + &((end)._c_opt_revision), + sizeof(self._c_range.end)) + +def canonicalize_path(path, scratch_pool=None, as_bytes=False): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef const char * _c_rpath + cdef object rpath + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + assert isinstance(path, bytes) + if scratch_pool is not None: + assert (scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create(&_c_tmp_pool, + (scratch_pool)._c_pool) + else: + _scratch_pool.clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, _scratch_pool._c_pool) + if ast: + raise PoolError() + try: + IF SVN_API_VER >= (1, 7): + if _c_.svn_path_is_url(path): + _c_rpath = _c_.svn_uri_canonicalize( + path, _c_tmp_pool) + rpath = _c_rpath + else: + _c_rpath = _c_.svn_dirent_canonicalize( + path, _c_tmp_pool) + rpath = _c_rpath + assert os.path.isabs(rpath) + ELSE: + _c_rpath = _c_.svn_path_canonicalize(path, _c_tmp_pool) + rpath = _c_rpath + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + IF PY_VERSION >= (3, 0, 0): + if not as_bytes: + return rpath.decode('utf-8', 'surrogateescape') + return rpath + +def canonicalize_rootpath(path, scratch_pool=None, as_bytes=False): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef const char * _c_rootpath + cdef object rootpath + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + assert isinstance(path, bytes) + if scratch_pool is not None: + assert (scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create(&_c_tmp_pool, + (scratch_pool)._c_pool) + else: + _scratch_pool.clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, _scratch_pool._c_pool) + if ast: + raise PoolError() + try: + if _c_.svn_path_is_url(path): + IF SVN_API_VER >= (1, 7): + _c_rootpath = _c_.svn_uri_canonicalize( + path, _c_tmp_pool) + ELSE: + _c_rootpath = _c_.svn_path_canonicalize( + path, _c_tmp_pool) + rootpath = _c_rootpath + if rootpath.lower().startswith(b'file:'): + IF SVN_API_VER >= (1, 7): + serr = _c_.svn_uri_get_dirent_from_file_url( + &_c_rootpath, rootpath, + _c_tmp_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + rootpath = _c_rootpath + ELSE: + rootpath_lower = rootpath.lower() + if rootpath_lower in [b'file://localhost', + b'file://localhost/', + b'file://', + b'file:///' + ]: + rootpath = b'/' + elif rootpath_lower.startswith(b'file://localhost/'): + rootpath = os.path.normpath( + urllib.unquote(rootpath[16:])) + else: + assert rootpath_lower.startswith(b'file:///') + rootpath = os.path.normpath( + urllib.unquote(rootpath[7:])) + assert os.path.isabs(rootpath) + else: + IF SVN_API_VER >= (1, 6): + _c_rootpath = _c_.svn_dirent_canonicalize( + path, _c_tmp_pool) + ELSE: + _c_rootpath = _c_.svn_path_canonicalize( + path, _c_tmp_pool) + rootpath = _c_rootpath + assert os.path.isabs(rootpath) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + IF PY_VERSION >= (3, 0, 0): + if not as_bytes: + return rootpath.decode('utf-8', 'surrogateescape') + ELSE: + return rootpath + +# called from svn_repos module +def rootpath2url(rootpath, path, scratch_pool=None): + cdef bytes fullpath + cdef const char * _c_dirent + cdef bytes dirent + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef const char * _c_url + cdef object url + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + + rootpath = os.path.abspath(rootpath) + if scratch_pool is not None: + fullpath = canonicalize_path(os.path.join(rootpath, path), + scratch_pool, as_bytes=True) + ast = _c_.apr_pool_create(&_c_tmp_pool, + (scratch_pool)._c_pool) + else: + _scratch_pool.clear() + fullpath = canonicalize_path(os.path.join(rootpath, path), + _scratch_pool, as_bytes=True) + _scratch_pool.clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, _scratch_pool._c_pool) + if ast: + raise PoolError() + try: + IF SVN_API_VER >= (1, 7): + _c_dirent = fullpath + serr = _c_.svn_uri_get_file_url_from_dirent( + &_c_url, _c_dirent, _c_tmp_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + url = _c_url + ELSE: + # implement what svn_uri_get_file_url_from_dirent has done + # from subversion/libsvn_subr/dirent.c (from subversion 1.10) + _c_dirent = _c_.svn_path_uri_encode(fullpath, _c_tmp_pool) + IF not SVN_USE_DOS_PATHS: + if _c_dirent[0] == ord(b'/') and _c_dirent[1] == 0: + url = b'file://' + else: + dirent = _c_dirent + url = b'file://' + dirent + ELSE: + if _c_dirent[0] == ord(b'/'): # expect UNC, not non-absolute + assert _c_dirent[1] == ord(b'/') + dirent = _c_dirent + url = b'file:' + dirent + else: + dirent = _c_dirent + url = b'file:///' + dirent + # "C:/" is a canonical dirent on Windows, + # but "file:///C:/' is not a canonical uri */ + if url[-1:] == b'/': + url = url[0:-1] + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return url + +# called from svn_repos module +def datestr_to_date(datestr, scratch_pool=None): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + cdef _c_.apr_time_t _c_when + cdef _c_datestr + + if scratch_pool is not None: + assert (scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create(&_c_tmp_pool, + (scratch_pool)._c_pool) + else: + _scratch_pool.clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, _scratch_pool._c_pool) + if ast: + raise PoolError() + + IF PY_VERSION >= (3, 0, 0): + if isinstance(datestr, str): + datestr = datestr.encode('utf-8', 'surrogateescape') + _c_datestr = datestr + try: + serr = _c_.svn_time_from_cstring( + &_c_when, _c_datestr, _c_tmp_pool) + if serr is not NULL: + _c_.svn_error_clear(serr) + when = None + else: + when = _c_when + when = when // 1000000 + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return when + +# from "svn_io.h" +cdef class svn_stream_t(object): + # cdef _c_.svn_stream_t * _c_ptr + def __cinit__(self): + self._c_ptr = NULL + cdef svn_stream_t set_stream( + svn_stream_t self, _c_.svn_stream_t * stream, object pool): + self._c_ptr = stream + assert pool is None or isinstance(pool, Apr_Pool) + self.pool = pool + return self + def close(self): + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + if self._c_ptr is not NULL: + serr = _c_.svn_stream_close(self._c_ptr) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + self.pool = None + self._c_ptr = NULL + + +cdef char _streambuf[_c_.SVN_STREAM_CHUNK_SIZE] + + +def svn_stream_read_full(svn_stream_t stream, len): + cdef _c_.apr_size_t _c_len + cdef char * _c_buf + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + # Note: this function read stream as bytes stream without decode + assert len > 0 + assert stream._c_ptr is not NULL + if len > _c_.SVN_STREAM_CHUNK_SIZE: + _c_buf = PyMem_Malloc(len) + else: + _c_buf = _streambuf + _c_len = len + IF SVN_API_VER >= (1, 9): + serr = _c_.svn_stream_read_full(stream._c_ptr, _c_buf, &_c_len) + ELSE: + serr = _c_.svn_stream_read(stream._c_ptr, _c_buf, &_c_len) + if serr is not NULL: + if _c_buf != _streambuf: + PyMem_Free(_c_buf) + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + if _c_len > 0: + buf = _c_buf[0:_c_len] + else: + buf = b'' + if _c_buf != _streambuf: + PyMem_Free(_c_buf) + return buf, _c_len + + +IF SVN_API_VER >= (1, 9): + def svn_stream_read2(svn_stream_t stream, len): + cdef _c_.apr_size_t _c_len + cdef char * _c_buf + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + # Note: this function read stream as bytes stream without decode + assert len > 0 + assert stream._c_ptr is not NULL + if len > _c_.SVN_STREAM_CHUNK_SIZE: + _c_buf = PyMem_Malloc(len) + else: + _c_buf = _streambuf + _c_len = len + IF SVN_API_VER >= (1, 9): + serr = _c_.svn_stream_read_full(stream._c_ptr, _c_buf, &_c_len) + ELSE: + serr = _c_.svn_stream_read(stream._c_ptr, _c_buf, &_c_len) + if serr is not NULL: + if _c_buf != _streambuf: + PyMem_Free(_c_buf) + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + if _c_len > 0: + buf = _c_buf[0:_c_len] + else: + buf = b'' + if _c_buf != _streambuf: + PyMem_Free(_c_buf) + return buf, _c_len + + +IF SVN_API_VER >= (1, 7): + def svn_stream_skip(svn_stream_t stream, len): + cdef _c_.apr_size_t _c_len + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + # Note: this function read stream as bytes stream without decode + assert len > 0 + assert stream._c_ptr is not NULL + _c_len = len + serr = _c_.svn_stream_skip(stream._c_ptr, _c_len) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + return + + +def svn_stream_write(svn_stream_t stream, const char * data, len): + cdef _c_.apr_size_t _c_len + cdef char * _c_buf + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + # Note: this function write to stream as bytes without encode/decode + assert len > 0 + assert stream._c_ptr is not NULL + _c_len = len + serr = _c_.svn_stream_write(stream._c_ptr, data, &_c_len) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + return _c_len + + +def svn_stream_close(svn_stream_t stream): + stream.close() + + +IF SVN_API_VER >= (1, 7): + cdef class svn_stream_mark_t(object): + cdef _c_.svn_stream_mark_t * _c_mark + def __cinit__(self): + self._c_mark = NULL + cdef inline svn_stream_mark_t set_mark( + svn_stream_mark_t self, _c_.svn_stream_mark_t * _c_mark): + self._c_mark = _c_mark + return self + cdef inline _c_.svn_stream_mark_t * get_mark(svn_stream_mark_t self): + return self._c_mark + + + def svn_stream_mark(svn_stream_t stream, object pool): + cdef _c_.svn_error_t * serr + cdef _c_.svn_stream_mark_t * _c_mark + cdef svn_stream_mark_t mark + cdef _c_.apr_pool_t * _c_pool + cdef Svn_error pyerr + if pool is not None: + assert (pool)._c_pool is not NULL + _c_pool = (pool)._c_pool + else: + _c_pool = _root_pool._c_pool + serr = _c_.svn_stream_mark(stream._c_ptr, &_c_mark, _c_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + mark = svn_stream_mark_t().set_mark(_c_mark) + return mark + + + def svn_stream_seek(svn_stream_t stream, object mark): + cdef _c_.svn_error_t * serr + cdef _c_.svn_stream_mark_t * _c_mark + cdef Svn_error pyerr + if mark is None: + _c_mark = NULL + else: + _c_mark = (mark)._c_mark + serr = _c_.svn_stream_seek(stream._c_ptr, _c_mark) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + return + + +IF SVN_API_VER >= (1, 9): + def svn_stream_data_available(svn_stream_t stream): + cdef _c_.svn_error_t * serr + cdef _c_.svn_boolean_t _c_avail + cdef Svn_error pyerr + serr = _c_.svn_stream_data_available(stream._c_ptr, &_c_avail) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + return True if _c_avail != _c_.FALSE else False + + +def svn_stream_readline( + svn_stream_t stream, const char *eol, scratch_pool=None): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + cdef _c_.svn_stringbuf_t * _c_stringbuf + cdef _c_.svn_boolean_t _c_eof + cdef object bufstr + cdef object eof + + assert (stream)._c_ptr is not NULL + if scratch_pool is not None: + assert (scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create(&_c_tmp_pool, + (scratch_pool)._c_pool) + else: + _scratch_pool.clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, _scratch_pool._c_pool) + if ast: + raise PoolError() + try: + serr = _c_.svn_stream_readline(stream._c_ptr, &_c_stringbuf, + eol, &_c_eof, _c_tmp_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + bufstr = _c_stringbuf.data[0:_c_stringbuf.len] + eof = True if _c_eof else False + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return bufstr, eof + + +# baton wrapper ... call back function helper +cdef class CbContainer(object): + def __cinit__(self, fnobj, btn, pool=None, **m): + assert callable(fnobj) + self.fnobj = fnobj + self.btn = btn + self.pool = pool + +# helper classes/functions to build python objects from C data structures +cdef class TransPtr(object): + cdef object to_object(self): + raise NotImplemented() + cdef void * from_object(self, object obj): + raise NotImplemented() + cdef void set_ptr(self, void *_c_ptr): + raise NotImplemented() + cdef void ** ptr_ref(self): + raise NotImplemented() + + +# warn: passing None as scratch_pool to constructor causes memory leak, +# because _scratch_pool cannot be used as substitute for it here, +# for the life time policy of the pool. The scratch_pool passed +# by constructor should be kept alive (this is achieved by reference count +# of the scratch_pool automatically) and should not be cleared until +# the HashTrans instance is alive. +cdef class HashTrans(TransPtr): + def __cinit__( + self, TransPtr key_trans, TransPtr val_trans, + scratch_pool=None, **m): + self.tmp_pool = None + self._c_hash = NULL + def __init__( + self, TransPtr key_trans, TransPtr val_trans, + scratch_pool=None, **m): + self.key_trans = key_trans + self.val_trans = val_trans + if scratch_pool is not None: + assert (scratch_pool)._c_pool is not NULL + self.tmp_pool = Apr_Pool(scratch_pool) + else: + self.tmp_pool = Apr_Pool(_root_pool) + cdef object to_object(self): + cdef _c_.apr_hash_index_t * hi + cdef const void * _c_key + cdef _c_.apr_ssize_t _c_klen + cdef void * _c_val + + if self._c_hash is NULL: + # Should we return None here? + return {} + self.tmp_pool.clear() + hi = _c_.apr_hash_first( + self.tmp_pool._c_pool, self._c_hash) + rdict = {} + while hi is not NULL: + _c_.apr_hash_this(hi, + (self.key_trans.ptr_ref()), + &_c_klen, + self.val_trans.ptr_ref()) + rdict[self.key_trans.to_object()] = self.val_trans.to_object() + hi = _c_.apr_hash_next(hi) + return rdict + cdef void set_ptr(self, void *_c_ptr): + self._c_hash = <_c_.apr_hash_t *>_c_ptr + cdef void ** ptr_ref(self): + return &(self._c_hash) + +cdef object hash_to_dict(_c_.apr_hash_t * _c_hash, + ptr_to_pyobj_func_t key_func, + ptr_to_pyobj_func_t val_func, + _c_.apr_pool_t * _c_scratch_pool): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.apr_hash_index_t * hi + cdef const void * _c_key + cdef _c_.apr_ssize_t _c_klen + cdef void * _c_val + + if _c_scratch_pool is NULL: + ast = _c_.apr_pool_create( + &_c_tmp_pool, _root_pool._c_pool) + else: + ast = _c_.apr_pool_create( + &_c_tmp_pool, _c_scratch_pool) + if ast: + raise PoolError() + try: + hi = _c_.apr_hash_first(_c_tmp_pool, _c_hash) + rdict = {} + while hi is not NULL: + _c_.apr_hash_this(hi, &_c_key, &_c_klen, &_c_val) + rdict[key_func(_c_key, _c_tmp_pool)] = \ + val_func(_c_val, _c_tmp_pool) + hi = _c_.apr_hash_next(hi) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return rdict + +cdef class CStringTransBytes(TransPtr): + def __cinit__(self): + self._c_str = NULL + cdef object to_object(self): + cdef object pybytes + pybytes = self._c_str + return pybytes + cdef void set_ptr(self, void *_c_ptr): + self._c_str = _c_ptr + cdef void ** ptr_ref(self): + return &(self._c_str) + +IF PY_VERSION >= (3, 0, 0): + cdef class CStringTransStr(TransPtr): + def __cinit__(self): + self._c_str = NULL + cdef object to_object(self): + cdef object pybytes + pybytes = self._c_str + return pybytes.decode('utf-8', 'surrogateescape') + cdef void set_ptr(self, void *_c_ptr): + self._c_str = _c_ptr + cdef void ** ptr_ref(self): + return &(self._c_str) + +ELSE: + cdef class CStringTransStr(CStringTransBytes): + pass + +cdef class SvnStringTransBytes(TransPtr): + def __cinit__(self): + self._c_svn_str = NULL + cdef object to_object(self): + cdef object pybytes + pybytes = self._c_svn_str[0].data[:self._c_svn_str[0].len] + return pybytes + cdef void set_ptr(self, void *_c_ptr): + self._c_svn_str = <_c_.svn_string_t *>_c_ptr + cdef void ** ptr_ref(self): + return &(self._c_svn_str) + +IF PY_VERSION >= (3, 0, 0): + cdef class SvnStringTransStr(TransPtr): + def __cinit__(self): + self._c_svn_str = NULL + cdef object to_object(self): + cdef object pybytes + pybytes = self._c_svn_str[0].data[:self._c_svn_str[0].len] + return pybytes.decode('utf-8', 'surrogateescape') + cdef void set_ptr(self, void *_c_ptr): + self._c_svn_str = <_c_.svn_string_t *>_c_ptr + cdef void ** ptr_ref(self): + return &(self._c_svn_str) + +ELSE: + cdef class SvnStringTransStr(SvnStringTransBytes): + pass + +cdef class SvnBooleanTrans(TransPtr): + cdef object to_object(self): + if self._c_bool != _c_.FALSE: + return True + return False + cdef void set_c_bool(self, _c_.svn_boolean_t _c_bool): + self._c_bool = _c_bool + cdef void ** ptr_ref(self): + return &(self._c_bool) + + +cdef _c_.apr_array_header_t * make_revnum_array( + object revisions, _c_.apr_pool_t * pool) except? NULL: + cdef _c_.apr_array_header_t * _c_arrayptr + cdef Svn_error pyerr + cdef object rev + cdef const char *strptr + cdef int nelts + + nelts = len(revisions) + _c_arrayptr = _c_.apr_array_make(pool, nelts, sizeof(_c_.svn_revnum_t)) + if _c_arrayptr is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + pyerr = Svn_error().seterror(_c_err) + raise SVNerr(pyerr) + for rev in revisions: + (<_c_.svn_revnum_t *>(_c_.apr_array_push(_c_arrayptr)))[0] = rev + return _c_arrayptr + + +cdef class SvnRevnumPtrTrans(TransPtr): + cdef object to_object(self): + return ((self._c_revptr)[0]) + cdef void set_c_revptr(self, _c_.svn_revnum_t * _c_revptr): + self._c_revptr = _c_revptr + cdef void ** ptr_ref(self): + return &(self._c_revptr) + + +# for test, not used by vclib modules +IF SVN_API_VER < (1, 4): + # from ^/subversion/tags/1.3.0/subversion/libsvn_subr/stream.c, + # private struct used by svn_stream_from_aprfile(), + # and may work on 1.11.0, but there is no warranty to work in future... + ctypedef struct baton_apr: + _c_.apr_file_t * file + _c_.apr_pool_t * pool + cdef _c_.svn_error_t * close_handler_apr(void *baton) with gil: + cdef baton_apr * btn + btn = baton + return _c_.svn_io_file_close(btn[0].file, btn[0].pool) + +def svn_stream_open_readonly( + const char * path, result_pool=None, scratch_pool=None): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef Apr_Pool r_pool + cdef _c_.svn_stream_t * _c_stream + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + IF SVN_API_VER < (1, 6): + cdef _c_.apr_file_t * _c_file + + if result_pool is not None: + assert (result_pool)._c_pool is not NULL + r_pool = result_pool + else: + r_pool = _root_pool + if scratch_pool is not None: + assert (scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create(&_c_tmp_pool, + (scratch_pool)._c_pool) + else: + _scratch_pool.clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, _scratch_pool._c_pool) + if ast: + raise PoolError() + try: + IF SVN_API_VER >= (1, 6): + serr = _c_.svn_stream_open_readonly( + &_c_stream, path, r_pool._c_pool, _c_tmp_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + ELSE: + serr = _c_.svn_io_file_open( + &_c_file, path, + _c_.APR_READ | _c_.APR_BUFFERED, + _c_.APR_OS_DEFAULT, r_pool._c_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + IF SVN_API_VER >= (1, 4): + _c_stream = _c_.svn_stream_from_aprfile2( + _c_file, _c_.FALSE, r_pool._c_pool) + ELSE: + _c_stream = _c_.svn_stream_from_aprfile( + _c_file, r_pool._c_pool) + _c_.svn_stream_set_close(_c_stream, close_handler_apr) + stream = svn_stream_t() + stream.set_stream(_c_stream, r_pool) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return stream + + +# least class to implement of buffer protocol for Python I/O +cdef class CharPtrWriteBuffer: + cdef CharPtrWriteBuffer set_buffer( + CharPtrWriteBuffer self, char * _c_buf, Py_ssize_t len): + self._c_buf = _c_buf + self.len = len + self.shape[0] = len + self.strides[0] = 1 + def __getbuffer__(self, Py_buffer * buffer, int flags): + buffer.buf = self._c_buf + buffer.obj = self + buffer.len = self.len + buffer.readonly = 0 + buffer.itemsize = 1 + buffer.format = NULL + buffer.ndim = 1 + buffer.shape = self.shape + buffer.strides = self.strides + buffer.suboffsets = NULL + buffer.internal = NULL + def __releasebuffer__(self, Py_buffer * buffer): + pass + + +cdef class CharPtrReadBuffer: + cdef CharPtrReadBuffer set_buffer( + CharPtrReadBuffer self, const char * _c_buf, Py_ssize_t len): + self._c_buf = _c_buf + self.len = len + self.shape[0] = len + self.strides[0] = 1 + + def __getbuffer__(self, Py_buffer * buffer, int flags): + buffer.buf = (self._c_buf) + buffer.obj = self + buffer.len = self.len + buffer.readonly = 1 + buffer.itemsize = 1 + buffer.format = NULL + buffer.ndim = 1 + buffer.shape = self.shape + buffer.strides = self.strides + buffer.suboffsets = NULL + buffer.internal = NULL + + def __releasebuffer__(self, Py_buffer * buffer): + pass + + +# baton for python stream wrapper +cdef class _py_stream_baton(object): + def __cinit__(self): + self.baton = None + IF SVN_API_VER >= (1, 7): + self.marks = {} + self.next_mark = 1 + +# an (least) implementation of svn_stream_t for Python I/O +# baton +cdef class _py_io_stream_baton(object): + def __cinit__(self, object fo): + self.fo = None + self.is_eof = True + def __init__(self, fo): + self.fo = fo + self.is_eof = False + +# callbacks +IF SVN_API_VER >= (1, 9): + cdef _c_.svn_error_t * _py_io_read_fn( + void * _c_baton, char * _c_buffer, + _c_.apr_size_t * _c_len) with gil: + cdef _py_io_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef _c_.apr_status_t ast + cdef char * emsg + cdef object err + cdef CharPtrWriteBuffer buf + cdef object len + + btn = <_py_io_stream_baton>_c_baton + _c_err = NULL + if btn.is_eof: + _c_len[0] = 0 + return _c_err + if _c_len[0] == 0: + return _c_err + # wrap the buffer pointer into buffer object + buf = CharPtrWriteBuffer.__new__(CharPtrWriteBuffer) + buf.set_buffer(_c_buffer, _c_len[0]) + try: + len = btn.fo.readinto(buf) + if len is None: + _c_len[0] = 0 + ast = _c_.APR_EAGAIN + _c_err = _c_.svn_error_create(ast, NULL, NULL) + else: + if len == 0: + btn.is_eof = True + _c_len[0] = len + except io.UnsuportedOperation as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + except io.BlockingIOError as err: + _c_len[0] = err.characters_written + emsg = NULL + if err.errno in (errno.EAGAIN, errno.EWOULDBLOCK): + ast = _c_.APR_EAGAIN + elif err.errno in (errno.EALREADY, errno.EINPROGRESS): + ast = _c_.APR_EINPROGRESS + else: + # unknown ... + ast = _c_.APR_EGENERAL + emsg = b'Unknown BlockingIOError on reading buffer' + _c_err = _c_.svn_error_create(ast, NULL, emsg) + except KeyboardInterrupt as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set while reading buffer: " + "%s" % str(err))) + finally: + del buf + return _c_err + + +cdef _c_.svn_error_t * _py_io_read_full_fn( + void * _c_baton, char * _c_buffer, _c_.apr_size_t * _c_len) with gil: + cdef _py_io_stream_baton btn + cdef CharPtrWriteBuffer buf + cdef object len + cdef char * _c_bp + cdef _c_.apr_size_t rest + cdef _c_.svn_error_t * _c_err + cdef object err + + btn = <_py_io_stream_baton>_c_baton + _c_err = NULL + if btn.is_eof: + _c_len[0] = 0 + return _c_err + + _c_bp = _c_buffer + rest = _c_len[0] + buf = CharPtrWriteBuffer.__new__(CharPtrWriteBuffer) + buf.set_buffer(_c_bp, rest) + try: + len = btn.fo.readinto(buf) + if len is None: + # no bytes are available in blocking mode + len = 0 + elif len == 0: + btn.is_eof = True + _c_len[0] = 0 + return _c_err + elif len == rest: + return _c_err + except io.BlockingIOError as err: + # blocking while reading: ignore and retry + len = err.characters_written + except io.UnsupportedOperation: + IF SVN_API_VER >= (1, 9): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL) + _c_len[0] = 0 + del buf + return _c_err + except KeyboardInterrupt as err: + _c_len[0] = err.characters_written + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + del buf + return _c_err + except Exception as err: + _c_len[0] = <_c_.apr_size_t>getattr(err, 'characters_written', 0) + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set while reading buffer: %s" + % str(err))) + del buf + return _c_err + rest -= len + _c_bp += len + buf.set_buffer(_c_bp, rest) + while True: + try: + len = btn.fo.readinto(buf) + if len is None: + continue + elif len == 0: + btn.is_eof = True + _c_len[0] -= rest + break + elif len == rest: + break + except io.BlockingIOError as err: + len = err.characters_written + if len == rest: + # may not happen: blocked but already read specified bytes. + break + except KeyboardInterrupt as err: + len = err.characters_written + rest -= len + _c_len[0] -= rest + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + break + except Exception as err: + len = <_c_.apr_size_t>getattr(err, 'characters_written', 0) + rest -= len + _c_len[0] -= rest + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set while reading buffer: %s" + % str(err))) + break + # end of try block + rest -= len + _c_bp += len + buf.set_buffer(_c_bp, rest) + # end of loop + + del buf + return _c_err + + +IF SVN_API_VER >= (1, 7): + cdef _c_.svn_error_t * _py_io_skip_with_seek_fn( + void * _c_baton, _c_.apr_size_t len) with gil: + cdef _py_io_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object err + + btn = <_py_io_stream_baton>_c_baton + _c_err = NULL + try: + btn.fo.seek(len, 1) + except io.UnsupportedOperation: + # fall back to without seek version + return _py_io_skip_without_seek_fn(_c_baton, len) + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in stream_skip: %s" + % str(err))) + return _c_err + + + cdef _c_.svn_error_t * _py_io_skip_without_seek_fn( + void * _c_baton, _c_.apr_size_t len) with gil: + cdef _py_io_stream_baton btn + cdef _c_.apr_size_t rest + cdef _c_.apr_size_t rlen + cdef object rbytes + cdef _c_.svn_error_t * _c_err + cdef object err + + btn = <_py_io_stream_baton>_c_baton + _c_err = NULL + rest = len + while rest > 0: + try: + rbytes, rlen = btn.fo.read(rest) + except io.UnsupportedOperation: + IF SVN_API_VER >= (1, 9): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL) + break + except io.BlockingIOError as err: + rlen = err.characters_written + if rlen == rest: + # may not happen: blocked but already read specified bytes. + break + except KeyboardInterrupt as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + break + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in stream_skip: %s" + % str(err))) + # end of try block + rest -= rlen + return _c_err + + +cdef _c_.svn_error_t * _py_io_write_fn( + void * _c_baton, const char * _c_buffer, + _c_.apr_size_t * _c_len) with gil: + cdef _py_io_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + cdef CharPtrReadBuffer buf + cdef object rlen + cdef const char * _c_bp + cdef _c_.apr_size_t rest + + btn = <_py_io_stream_baton>_c_baton + _c_err = NULL + if btn.is_eof: + _c_len[0] = 0 + _c_err = _c_.svn_error_create( + _c_.APR_EOF, NULL, NULL) + return _c_err + + buf = CharPtrReadBuffer.__new__(CharPtrReadBuffer) + _c_bp = _c_buffer + rest = _c_len[0] + while rest > 0: + buf.set_buffer(_c_bp, rest) + try: + rlen = btn.fo.write(buf) + if rlen is None or rlen == 0: + continue + except io.UnsupportedOperation: + IF SVN_API_VER >= (1, 9): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL) + break + except io.BlockingIOError as err: + rlen = err.characters_written + if rlen == rest: + # may not happen: blocked but already write specified bytes. + rest = 0 + break + except KeyboardInterrupt as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + break + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set while writing buffer: " + "%s" % str(err))) + # end of try block + rest -= rlen + _c_bp += rlen + # end of while + _c_len[0] -= rest + del buf + return _c_err + + +cdef _c_.svn_error_t * _py_io_close_fn(void * _c_baton) with gil: + cdef _py_io_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + + btn = <_py_io_stream_baton>_c_baton + _c_err = NULL + try: + btn.fo.close() + btn.is_eof = True + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in " + "stream_close: %s" % str(err))) + return _c_err + + +IF SVN_API_VER >= (1, 7): + cdef _c_.svn_error_t * _py_io_mark_fn( + void * _c_baton, _c_.svn_stream_mark_t ** _c_mark, + _c_.apr_pool_t * _c_pool) with gil: + cdef _py_io_stream_baton btn + cdef object mark + cdef _c_.svn_error_t * _c_err + cdef object err + + btn = <_py_io_stream_baton>_c_baton + _c_err = NULL + try: + mark = btn.fo.tell() + btn.marks[btn.next_mark] = mark + assert sizeof(void *) >= sizeof(int) + (_c_mark)[0]= (btn.next_mark) + btn.next_mark += 1 + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in " + "stream_mark: %s" % str(err))) + return _c_err + + + cdef _c_.svn_error_t * _py_io_seek_fn( + void * _c_baton, const _c_.svn_stream_mark_t * _c_mark) with gil: + cdef _py_io_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object err + cdef object mark + + btn = <_py_io_stream_baton>_c_baton + _c_err = NULL + try: + if _c_mark is NULL: + mark = 0 + else: + mark_key = _c_mark + mark = btn.marks[mark_key] + btn.fo.seek(mark, 0) + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in " + "stream_seek: %s" % str(err))) + return _c_err + +# svn_stream_t based on Python io.RawIO or io.BufferdIO +cdef class py_io_stream(svn_stream_t): + def __cinit__(self, object fo, object pool, **m): + pass + def __init__(self, object fo, object pool, **m): + assert isinstance(fo, io.IOBase) + + if pool is not None: + assert (pool)._c_pool is not NULL + self.pool = pool + else: + self.pool = _root_pool + self.baton = _py_io_stream_baton(fo) + self._c_ptr = _c_.svn_stream_create( + (self.baton), self.pool._c_pool) + # check io capability and set callbacks + if self.baton.fo.readable(): + IF SVN_API_VER >= (1, 9): + _c_.svn_stream_set_read2( + self._c_ptr, _py_io_read_fn, _py_io_read_full_fn) + ELSE: + _c_.svn_stream_set_read( + self._c_ptr, _py_io_read_full_fn) + if self.baton.fo.seekable(): + _c_.svn_stream_set_skip(self._c_ptr, _py_io_skip_with_seek_fn) + else: + _c_.svn_stream_set_skip( + self._c_ptr, _py_io_skip_without_seek_fn) + else: + IF SVN_API_VER >= (1, 9): + _c_.svn_stream_set_read2( + self._c_ptr, NULL, NULL) + ELSE: + _c_.svn_stream_set_read( + self._c_ptr, NULL) + _c_.svn_stream_set_seek(self._c_ptr, NULL) + if self.baton.fo.writable(): + _c_.svn_stream_set_write(self._c_ptr, _py_io_write_fn) + else: + _c_.svn_stream_set_write(self._c_ptr, NULL) + _c_.svn_stream_set_close(self._c_ptr, _py_io_close_fn) + IF SVN_API_VER >= (1, 7): + if self.baton.fo.seekable(): + _c_.svn_stream_set_mark(self._c_ptr, _py_io_mark_fn) + _c_.svn_stream_set_seek(self._c_ptr, _py_io_seek_fn) + else: + _c_.svn_stream_set_mark(self._c_ptr, NULL) + _c_.svn_stream_set_seek(self._c_ptr, NULL) + IF SVN_API_VER >= (1, 9): + # we don't support svn_stream_data_available() + _c_.svn_stream_set_data_available(self._c_ptr, NULL) + IF SVN_API_VER >= (1, 10): + # we don't support svn_stream_readline() directly, simply + # because io.BaseIO.readline() is not available here for + # 'eol' argument, and subversion's internal fall back + # function may be faster than implementation using Cython. + _c_.svn_stream_set_readline(self._c_ptr, NULL) + + +# baton for implement generic svn_stream_t with Python +cdef class _py_generic_stream_baton(_py_stream_baton): + def __cinit__(self): + self.read_fn = None + IF SVN_API_VER >= (1, 9): + self.read_full_fn = None + IF SVN_API_VER >= (1, 7): + self.skip_fn = None + self.write_fn = None + self.close_fn = None + IF SVN_API_VER >= (1, 7): + self.mark_fn = None + self.seek_fn = None + IF SVN_API_VER >= (1, 9): + self.data_available_fn = None + IF SVN_API_VER >= (1, 10): + self.readline_fn = None + +# warn: for API version 1.8 and below, read_fn should read full length or +# to EOF. And for API version 1.9 and above, read_fn is used for +# svn_stream_read2() (may support partial read) +cdef _c_.svn_error_t * _py_read_fn( + void * _c_baton, char * _c_buffer, _c_.apr_size_t * _c_len) with gil: + cdef _py_generic_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + cdef CharPtrWriteBuffer buf + + btn = <_py_generic_stream_baton>_c_baton + _c_err = NULL + if btn.read_fn is None: + IF SVN_API_VER >= (1, 9): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL) + return _c_err + # wrap the buffer pointer into buffer object + buf = CharPtrWriteBuffer.__new__(CharPtrWriteBuffer) + buf.set_buffer(_c_buffer, _c_len[0]) + try: + _c_len[0] = btn.read_fn(btn.baton, buf, _c_len[0]) + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set while reading buffer: %s" + % str(err))) + finally: + del buf + return _c_err + + +IF SVN_API_VER >= (1, 9): + cdef _c_.svn_error_t * _py_read_full_fn( + void * _c_baton, char * _c_buffer, + _c_.apr_size_t * _c_len) with gil: + cdef _py_generic_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + cdef CharPtrWriteBuffer buf + + btn = <_py_generic_stream_baton>_c_baton + _c_err = NULL + if btn.read_full_fn is None: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + return _c_err + # wrap the buffer pointer into buffer object + buf = CharPtrWriteBuffer.__new__(CharPtrWriteBuffer) + buf.set_buffer(_c_buffer, _c_len[0]) + try: + _c_len[0] = btn.read_full_fn(btn.baton, buf, _c_len[0]) + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set while reading buffer: " + "%s" % str(err))) + finally: + del buf + return _c_err + + +IF SVN_API_VER >= (1, 7): + cdef _c_.svn_error_t * _py_skip_fn( + void * _c_baton, _c_.apr_size_t len) with gil: + cdef _py_generic_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + + btn = <_py_generic_stream_baton>_c_baton + _c_err = NULL + if btn.skip_fn is None: + IF SVN_API_VER >= (1, 9): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL) + return _c_err + try: + btn.skip_fn(btn.baton, len) + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in stream_skip: %s" + % str(err))) + return _c_err + + +cdef _c_.svn_error_t * _py_write_fn( + void * _c_baton, const char * _c_buffer, + _c_.apr_size_t * _c_len) with gil: + cdef _py_generic_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + cdef CharPtrReadBuffer buf + cdef object len + + btn = <_py_generic_stream_baton>_c_baton + _c_err = NULL + if btn.write_fn is None: + IF SVN_API_VER >= (1, 9): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL) + return _c_err + # wrap the buffer pointer into buffer object + buf = CharPtrReadBuffer.__new__(CharPtrReadBuffer) + buf.set_buffer(_c_buffer, _c_len[0]) + try: + len = btn.write_fn(btn.baton, buf, _c_len[0]) + _c_len[0] = len + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set while writing buffer: %s" + % str(err))) + finally: + del buf + return _c_err + + +cdef _c_.svn_error_t * _py_close_fn(void * _c_baton) with gil: + cdef _py_generic_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + + btn = <_py_generic_stream_baton>_c_baton + _c_err = NULL + if btn.close_fn is not None: + try: + btn.close_fn(btn.baton) + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in " + "stream_close: %s" % str(err))) + return _c_err + + +IF SVN_API_VER >= (1, 7): + cdef _c_.svn_error_t * _py_mark_fn( + void * _c_baton, _c_.svn_stream_mark_t ** _c_mark, + _c_.apr_pool_t * _c_pool) with gil: + cdef _py_generic_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + + btn = <_py_generic_stream_baton>_c_baton + _c_err = NULL + if btn.mark_fn is None: + IF SVN_API_VER >= (1, 9): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL) + return _c_err + try: + mark = btn.mark_fn(btn.baton) + btn.marks[btn.next_mark] = mark + assert sizeof(void *) >= sizeof(int) + (_c_mark)[0]= (btn.next_mark) + btn.next_mark += 1 + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in " + "stream_mark: %s" % str(err))) + return _c_err + + + cdef _c_.svn_error_t * _py_seek_fn( + void * _c_baton, const _c_.svn_stream_mark_t * _c_mark) with gil: + cdef _py_generic_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + cdef object mark + + btn = <_py_generic_stream_baton>_c_baton + _c_err = NULL + if btn.seek_fn is None: + IF SVN_API_VER >= (1, 9): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL) + return _c_err + try: + if _c_mark is NULL: + mark = None + else: + mark_key = _c_mark + mark = btn.marks[mark_key] + btn.seek_fn(btn.baton, mark) + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in " + "stream_seek: %s" % str(err))) + return _c_err + + +IF SVN_API_VER >= (1, 9): + cdef _c_.svn_error_t * _py_data_available_fn( + void * _c_baton, _c_.svn_boolean_t * _c_data_available) with gil: + cdef _py_generic_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + + btn = <_py_generic_stream_baton>_c_baton + _c_err = NULL + if btn.data_available_fn is None: + IF SVN_API_VER >= (1, 9): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL) + return _c_err + try: + if btn.data_available_fn(btn.baton): + _c_data_available[0] = _c_.TRUE + else: + _c_data_available[0] = _c_.FALSE + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in " + "stream_data_available: %s" % str(err))) + return _c_err + + +IF SVN_API_VER >= (1, 10): + cdef _c_.svn_error_t * _py_readline_fn( + void * _c_baton, _c_.svn_stringbuf_t ** _c_stringbuf, + const char * _c_eol, _c_.svn_boolean_t * _c_eof, + _c_.apr_pool_t * pool) with gil: + cdef _py_generic_stream_baton btn + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef object err + cdef Svn_error svnerr + cdef object eol + cdef Apr_Pool w_pool + + btn = <_py_generic_stream_baton>_c_baton + _c_err = NULL + if btn.readline_fn is None: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL) + return _c_err + eol = _c_eol + w_pool = Apr_Pool.__new__(Apr_Pool, None) + w_pool.set_pool(pool) + try: + buf, eof = btn.readline_fn(btn.baton, eol, w_pool) + _c_stringbuf[0] = _c_.svn_stringbuf_create( + buf, pool) + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except Exception as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL, + ("Python exception has been set in " + "stream_readline: %s" % str(err))) + return _c_err + +cdef class py_stream(svn_stream_t): + def __init__(self, pool=None): + if pool is not None: + assert (pool)._c_pool is not NULL + self.pool = pool + else: + self.pool = _root_pool + self.baton = _py_generic_stream_baton() + self._c_ptr = _c_.svn_stream_create( + (self.baton), self.pool._c_pool) + def set_baton(self, baton): + self.baton.baton = baton + IF SVN_API_VER >= (1, 9): + def set_read(self, read_fn=None, read_full_fn=None): + if read_fn or read_full_fn: + _c_.svn_stream_set_read2( + self._c_ptr, _py_read_fn, _py_read_full_fn) + else: + _c_.svn_stream_set_read2( + self._c_ptr, NULL, NULL) + self.baton.read_fn = read_fn + self.baton.read_full_fn = read_full_fn + ELSE: + def set_read(self, read_fn=None): + if read_fn: + _c_.svn_stream_set_read(self._c_ptr, _py_read_fn) + else: + _c_.svn_stream_set_read(self._c_ptr, NULL) + self.baton.read_fn = read_fn + IF SVN_API_VER >= (1, 7): + def set_skip(self, skip_fn=None): + if skip_fn: + _c_.svn_stream_set_skip(self._c_ptr, _py_skip_fn) + else: + _c_.svn_stream_set_skip(self._c_ptr, NULL) + self.baton.skip_fn = skip_fn + def set_write(self, write_fn=None): + if write_fn: + _c_.svn_stream_set_write(self._c_ptr, _py_write_fn) + else: + _c_.svn_stream_set_write(self._c_ptr, NULL) + self.baton.write_fn = write_fn + def set_close(self, close_fn=None): + if close_fn: + _c_.svn_stream_set_close(self._c_ptr, _py_close_fn) + else: + _c_.svn_stream_set_close(self._c_ptr, NULL) + self.baton.close_fn = close_fn + IF SVN_API_VER >= (1, 7): + def set_mark(self, mark_fn=None): + if mark_fn: + _c_.svn_stream_set_mark(self._c_ptr, _py_mark_fn) + else: + _c_.svn_stream_set_mark(self._c_ptr, NULL) + self.baton.mark_fn = mark_fn + def set_seek(self, seek_fn=None): + if seek_fn: + _c_.svn_stream_set_seek(self._c_ptr, _py_seek_fn) + else: + _c_.svn_stream_set_seek(self._c_ptr, NULL) + self.baton.seek_fn = seek_fn + IF SVN_API_VER >= (1, 9): + def set_data_available(self, data_available_fn=None): + if data_available_fn: + _c_.svn_stream_set_data_available( + self._c_ptr, _py_data_available_fn) + else: + _c_.svn_stream_set_data_available(self._c_ptr, NULL) + self.baton.data_available_fn = data_available_fn + IF SVN_API_VER >= (1, 10): + def set_readline(self, readline_fn=None): + if readline_fn: + _c_.svn_stream_set_readline(self._c_ptr, _py_readline_fn) + else: + _c_.svn_stream_set_readline(self._c_ptr, NULL) + self.baton.readline_fn = readline_fn + + +cdef class svn_client_ctx_t(object): + # cdef _c_.svn_client_ctx_t * _c_ctx + # cdef Apr_Pool pool + cdef svn_client_ctx_t set_ctx(self, _c_.svn_client_ctx_t * _c_ctx, pool): + assert pool is None or (pool)._c_pool is not NULL + self.pool = pool + assert _c_ctx is not NULL + self._c_ctx = _c_ctx + return self + + +# custom version of svn_client_ctx*() +cpdef svn_client_ctx_t setup_client_ctx( + object config_dir, object result_pool=None): + cdef const char * _c_config_dir + cdef Apr_Pool r_pool + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + cdef _c_.apr_hash_t * _c_cfg_hash + cdef _c_.svn_client_ctx_t * _c_ctx + cdef _c_.svn_auth_baton_t * _c_auth_baton + cdef svn_client_ctx_t ctx + IF SVN_API_VER >= (1, 6): + cdef _c_.svn_config_t * _c_cfg + ELSE: + cdef _c_.apr_array_header_t * _c_providers + cdef _c_.svn_auth_provider_object_t * _c_provider + + if result_pool is not None: + assert (result_pool)._c_pool is not NULL + r_pool = result_pool + else: + r_pool = _root_pool + assert isinstance(config_dir, bytes) or config_dir is None + _c_config_dir = config_dir if config_dir else NULL + + serr = _c_.svn_config_ensure(_c_config_dir, r_pool._c_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + serr = _c_.svn_config_get_config( + &_c_cfg_hash, _c_config_dir, r_pool._c_pool) + + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + IF SVN_API_VER >= (1, 8): + serr = _c_.svn_client_create_context2( + &_c_ctx, _c_cfg_hash, r_pool._c_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + ELSE: + serr = _c_.svn_client_create_context(&_c_ctx, r_pool._c_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + _c_ctx[0].config = _c_cfg_hash + IF SVN_API_VER >= (1, 6): + _c_cfg = <_c_.svn_config_t *>_c_.apr_hash_get( + _c_cfg_hash, _c_.SVN_CONFIG_CATEGORY_CONFIG, + _c_.APR_HASH_KEY_STRING) + IF SVN_API_VER >= (1, 9): + serr = _c_.svn_cmdline_create_auth_baton2( + &_c_auth_baton, _c_.TRUE, NULL, NULL, + _c_config_dir, _c_.TRUE, + _c_.TRUE, _c_.TRUE, _c_.TRUE, _c_.TRUE, _c_.TRUE, + _c_cfg, NULL, NULL, r_pool._c_pool) + ELSE: + serr = _c_.svn_cmdline_create_auth_baton( + &_c_auth_baton, _c_.TRUE, NULL, NULL, + _c_config_dir, _c_.TRUE, _c_.TRUE, + _c_cfg, NULL, NULL, r_pool._c_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + ELSE: + _c_providers = _c_.apr_array_make( + r_pool._c_pool, 5, + sizeof(_c_.svn_auth_provider_object_t *)) + if _c_providers is NULL: + serr = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + IF SVN_API_VER >= (1, 4): + _c_.svn_auth_get_simple_provider(&_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + _c_.svn_auth_get_username_provider(&_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + _c_.svn_auth_get_ssl_server_trust_file_provider( + &_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + _c_.svn_auth_get_ssl_client_cert_file_provider( + &_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + _c_.svn_auth_get_ssl_client_cert_pw_file_provider( + &_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + ELSE: + _c_.svn_client_get_simple_provider(&_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + _c_.svn_client_get_username_provider(&_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + _c_.svn_client_get_ssl_server_trust_file_provider( + &_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + _c_.svn_client_get_ssl_client_cert_file_provider( + &_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + _c_.svn_client_get_ssl_client_cert_pw_file_provider( + &_c_provider, r_pool._c_pool) + ((_c_.apr_array_push(_c_providers)))[0] = ( + _c_provider) + _c_.svn_auth_open(&_c_auth_baton, _c_providers, r_pool._c_pool) + _c_ctx[0].auth_baton = _c_auth_baton + ctx = svn_client_ctx_t().set_ctx(_c_ctx, r_pool) + return ctx + +# _get_annotated_source() ... helper for LocalSubversionRepository.annotate() +# custom baton for _get_annotated_source() +cdef class CbBlameContainer(object): + cdef object fnobj + cdef public object btn + cdef public _c_.svn_revnum_t first_rev + cdef public object include_text + def __cinit__( + self, fnobj, btn, first_rev=_c_.SVN_INVALID_REVNUM, + include_text=False, **m): + self.fnobj = fnobj + self.btn = btn + self.first_rev = first_rev + self.include_text = include_text + +# call back functions for _get_annotated_source +IF SVN_API_VER >= (1, 7): + # svn_client_blame_receiver3_t + cdef _c_.svn_error_t * _cb_get_annotated_source3( + void * _c_baton, + _c_.svn_revnum_t _c_start_revnum, _c_.svn_revnum_t _c_end_revnum, + _c_.apr_int64_t _c_line_no, + _c_.svn_revnum_t _c_revision, _c_.apr_hash_t * _c_rev_props, + _c_.svn_revnum_t _c_merged_revision, + _c_.apr_hash_t * _c_merged_rev_props, + const char * _c_merged_path, + const char * _c_line, _c_.svn_boolean_t _c_local_change, + _c_.apr_pool_t * _c_pool) with gil: + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef Svn_error svnerr + cdef CbBlameContainer btn + cdef SvnStringTransStr trans_svn_string + cdef SvnStringTransBytes trans_svn_string_bytes + cdef _c_.svn_string_t * _c_author + cdef object author + cdef _c_.svn_string_t * _c_date_string + cdef object date_string + cdef _c_.apr_time_t _c_date + cdef object date + + btn = _c_baton + # extract author + _c_author = <_c_.svn_string_t *>_c_.apr_hash_get( + _c_rev_props, _c_.SVN_PROP_REVISION_AUTHOR, + _c_.APR_HASH_KEY_STRING) + trans_svn_string = SvnStringTransStr() + trans_svn_string.set_ptr(_c_author) + author = trans_svn_string.to_object() + # extract date + date = None + _c_date_string = <_c_.svn_string_t *>_c_.apr_hash_get( + _c_rev_props, _c_.SVN_PROP_REVISION_DATE, + _c_.APR_HASH_KEY_STRING) + if _c_date_string is not NULL: + trans_svn_string_bytes = SvnStringTransBytes() + trans_svn_string_bytes.set_ptr(_c_date_string) + date_string = trans_svn_string_bytes.to_object() + if date_string: + _c_err = _c_.svn_time_from_cstring( + &_c_date, date_string, _c_pool) + if _c_err is NULL: + date = (_c_date // 1000000) + _c_err = NULL + try: + btn.fnobj(btn, _c_line_no, _c_revision, author, date, + _c_line) + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except AssertionError as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, str(err)) + except KeyboardInterrupt as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + except BaseException as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_BASE, NULL, str(err)) + return _c_err +ELIF SVN_API_VER >= (1, 5): + # svn_client_blame_receiver2_t + cdef _c_.svn_error_t * _cb_get_annotated_source2( + void * _c_baton, _c_.apr_int64_t _c_line_no, + _c_.svn_revnum_t _c_revision, + const char * _c_author, const char * _c_date_string, + _c_.svn_revnum_t _c_merged_revision, const char * _c_merged_author, + const char * _c_merged_date_string, const char * _c_merged_path, + const char * _c_line, _c_.apr_pool_t * _c_pool) with gil: + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef Svn_error svnerr + cdef CbBlameContainer btn + cdef object author + cdef _c_.apr_time_t _c_date + cdef object date + + btn = _c_baton + if _c_author is NULL: + author = '' + else: + author = _c_author + IF PY_VERSION >= (3, 0, 0): + author = _norm(author) + # extract date + if _c_date_string is not NULL and _c_date_string[0] != 0: + _c_err = _c_.svn_time_from_cstring( + &_c_date, _c_date_string, _c_pool) + if _c_err is NULL: + date = (_c_date // 1000000) + else: + date = None + _c_err = NULL + try: + btn.fnobj(btn, _c_line_no, _c_revision, author, date, + _c_line) + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except AssertionError as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, str(err)) + except KeyboardInterrupt as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + except BaseException as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_BASE, NULL, str(err)) + return _c_err +ELSE: + # svn_client_blame_receiver_t + # just same as _cb_get_annotated_source2 except function signature + cdef _c_.svn_error_t * _cb_get_annotated_source( + void * _c_baton, _c_.apr_int64_t _c_line_no, + _c_.svn_revnum_t _c_revision, + const char * _c_author, const char * _c_date_string, + const char * _c_line, _c_.apr_pool_t * _c_pool) with gil: + cdef _c_.svn_error_t * _c_err + cdef object serr + cdef Svn_error svnerr + cdef CbBlameContainer btn + cdef object author + cdef _c_.apr_time_t _c_date + cdef object date + + btn = _c_baton + if _c_author is NULL: + author = '' + else: + author = _c_author + IF PY_VERSION >= (3, 0, 0): + author = _norm(author) + # extract date + if _c_date_string is not NULL and _c_date_string[0] != 0: + _c_err = _c_.svn_time_from_cstring( + &_c_date, _c_date_string, _c_pool) + if _c_err is NULL: + date = (_c_date // 1000000) + else: + date = None + _c_err = NULL + try: + btn.fnobj(btn, _c_line_no, _c_revision, author, date, + _c_line) + except SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except AssertionError as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, str(err)) + except KeyboardInterrupt as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + except BaseException as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_BASE, NULL, str(err)) + return _c_err + +def _get_annotated_source( + object path_or_url, object rev, object oldest_rev, + object blame_func, svn_client_ctx_t ctx, object include_text=False, + object scratch_pool=None): + cdef const char * _c_path_or_url + cdef svn_opt_revision_t opt_rev + cdef svn_opt_revision_t opt_oldest_rev + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_error_t * serr + cdef Svn_error pyerr + cdef _c_.apr_hash_t * _c_cfg_hash + cdef _c_.apr_array_header_t * _c_empty_array + cdef _c_.svn_auth_baton_t * _c_auth_baton + cdef list ann_list + cdef CbBlameContainer btn + IF SVN_API_VER >= (1, 5): + cdef _c_.svn_diff_file_options_t * _c_diff_opt + + # make sure path_or_url is a bytes object + if isinstance(path_or_url, PathLike): + path_or_url = path_or_url.__fspath__() + if not isinstance(path_or_url, bytes) and isinstance(path_or_url, str): + path_or_url = path_or_url.encode('utf-8') + _c_path_or_url = path_or_url + opt_rev = svn_opt_revision_t(_c_.svn_opt_revision_number, rev) + opt_oldest_rev = svn_opt_revision_t(_c_.svn_opt_revision_number, + oldest_rev) + assert callable(blame_func) + if scratch_pool is not None: + assert (scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (scratch_pool)._c_pool) + else: + (_scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (_scratch_pool)._c_pool) + if ast: + raise PoolError() + try: + ann_list = [] + btn = CbBlameContainer( + blame_func, ann_list, oldest_rev, include_text) + IF SVN_API_VER >= (1, 4): + _c_diff_opt = _c_.svn_diff_file_options_create(_c_tmp_pool) + IF SVN_API_VER >= (1, 7): + serr = _c_.svn_client_blame5( + _c_path_or_url, + &(opt_rev._c_opt_revision), + &(opt_oldest_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + _c_diff_opt, _c_.FALSE, _c_.FALSE, + _cb_get_annotated_source3, btn, + ctx._c_ctx, _c_tmp_pool) + ELIF SVN_API_VER >= (1, 5): + serr = _c_.svn_client_blame4( + _c_path_or_url, + &(opt_rev._c_opt_revision), + &(opt_oldest_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + _c_diff_opt, _c_.FALSE, _c_.FALSE, + _cb_get_annotated_source2, btn, + ctx._c_ctx, _c_tmp_pool) + ELIF SVN_API_VER >= (1, 4): + serr = _c_.svn_client_blame3( + _c_path_or_url, + &(opt_rev._c_opt_revision), + &(opt_oldest_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + _c_diff_opt, _c_.FALSE, + _cb_get_annotated_source, btn, + ctx._c_ctx, _c_tmp_pool) + ELSE: + serr = _c_.svn_client_blame2( + _c_path_or_url, + &(opt_rev._c_opt_revision), + &(opt_oldest_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + _cb_get_annotated_source, btn, + ctx._c_ctx, _c_tmp_pool) + if serr is not NULL: + pyerr = Svn_error().seterror(serr) + raise SVNerr(pyerr) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return ann_list diff --git a/src/lib/vclib/altsvn/_svn_capi.pxd b/src/lib/vclib/altsvn/_svn_capi.pxd new file mode 100644 index 00000000..e513fe63 --- /dev/null +++ b/src/lib/vclib/altsvn/_svn_capi.pxd @@ -0,0 +1,26 @@ +include "_svn_api_ver.pxi" +from apr_1.apr cimport * +from apr_1.apr_errno cimport * +from apr_1.apr_general cimport * +from apr_1.apr_pools cimport * +from apr_1.apr_time cimport * +from apr_1.apr_hash cimport * +from apr_1.apr_tables cimport * +IF SVN_API_VER < (1, 6): + from apr_1.apr_file_io cimport * +from subversion_1.svn_types cimport * +from subversion_1.svn_error cimport * +from subversion_1.svn_error_codes cimport * +from subversion_1.svn_props cimport * +from subversion_1.svn_version cimport * +from subversion_1.svn_opt cimport * +from subversion_1.svn_path cimport * +from subversion_1.svn_time cimport * +from subversion_1.svn_string cimport * +from subversion_1.svn_io cimport * +IF SVN_API_VER >= (1, 6): + from subversion_1.svn_dirent_uri cimport * + from subversion_1.svn_cmdline cimport * +from subversion_1.svn_diff cimport * +from subversion_1.svn_config cimport * +from subversion_1.svn_client cimport * diff --git a/src/lib/vclib/altsvn/_svn_ra.pxd b/src/lib/vclib/altsvn/_svn_ra.pxd new file mode 100644 index 00000000..c5c7c53f --- /dev/null +++ b/src/lib/vclib/altsvn/_svn_ra.pxd @@ -0,0 +1,51 @@ +include "_svn_api_ver.pxi" +cimport _svn_ra_capi as _c_ +cimport _svn + +cdef class svn_ra_session_t(object): + cdef _c_.svn_ra_session_t * _c_session + cdef _svn.Apr_Pool pool + cdef svn_ra_session_t set_session( + self, _c_.svn_ra_session_t * _c_session, pool) + +IF SVN_API_VER >= (1, 6): + cdef class py_svn_log_changed_path2_ref(object): + cdef public object action + cdef public object copyfrom_path + cdef public object copyfrom_rev + cdef public object node_kind + IF SVN_API_VER >= (1, 7): + cdef public object text_modified + cdef public object props_modified + cdef py_svn_log_changed_path2_ref bind( + py_svn_log_changed_path2_ref self, + _c_.svn_log_changed_path2_t * _c_ptr) + +cdef class py_svn_log_changed_path_ref(object): + cdef public object action + cdef public object copyfrom_path + cdef public object copyfrom_rev + cdef py_svn_log_changed_path_ref bind( + py_svn_log_changed_path_ref self, + _c_.svn_log_changed_path_t * _c_ptr) + +cdef class py_svn_log_entry(object): + cdef public object changed_paths + cdef public object revision + cdef public object revprops + IF SVN_API_VER >= (1, 5): + cdef public object has_children + IF SVN_API_VER >= (1, 6): + cdef public object changed_paths2 + IF SVN_API_VER >= (1, 7): + cdef public object non_inheritable + cdef public object subtractive_merge + IF SVN_API_VER >= (1, 5): + cdef void bind(self, const _c_.svn_log_entry_t *_c_ptr, + _svn.Apr_Pool scratch_pool) + ELSE: + cdef void bind( + self, const _c_.apr_hash_t *_c_changed_paths, + _c_.svn_revision_t _c_revision, const char * author, + const char * date, const char * message, + _svn.Apr_Pool scratch_pool) diff --git a/src/lib/vclib/altsvn/_svn_ra.pyx b/src/lib/vclib/altsvn/_svn_ra.pyx new file mode 100644 index 00000000..e57dbc03 --- /dev/null +++ b/src/lib/vclib/altsvn/_svn_ra.pyx @@ -0,0 +1,1314 @@ +include "_svn_api_ver.pxi" +include "_py_ver.pxi" +cimport _svn_ra_capi as _c_ +cimport _svn +cimport _svn_repos +import os +from . import _svn + +try: + import os + PathLike = os.PathLike +except AttributeError: + class PathLike(object): + pass + +def _ra_init(): + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + serr = _c_.svn_ra_initialize(_svn._root_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + +_ra_init() +del _ra_init + + +cdef class svn_ra_session_t(object): + # cdef _c_.svn_ra_session_t * _c_session + # cdef _svn.Apr_Pool pool + cdef svn_ra_session_t set_session( + self, _c_.svn_ra_session_t * _c_session, pool): + assert pool is None or (<_svn.Apr_Pool?>pool)._c_pool is not NULL + self.pool = pool + assert _c_session is not NULL + self._c_session = _c_session + return self + + +def svn_ra_get_latest_revnum(svn_ra_session_t session, scratch_pool=None): + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.apr_status_t ast + cdef _c_.svn_revnum_t _c_rev + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create(&_c_tmp_pool, + (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + serr = _c_.svn_ra_get_latest_revnum( + session._c_session, &_c_rev, _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return _c_rev + + +def svn_ra_check_path( + svn_ra_session_t session, object path, + _c_.svn_revnum_t _c_revision, object scratch_pool=None): + cdef const char * _c_path + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef _c_.svn_node_kind_t _c_kind + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create(&_c_tmp_pool, + (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + serr = _c_.svn_ra_check_path( + session._c_session, _c_path, _c_revision, &_c_kind, + _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return _c_kind + + +def svn_ra_get_locations( + svn_ra_session_t session, object path, + _c_.svn_revnum_t _c_peg_revision, object location_revisions, + object scratch_pool): + cdef const char * _c_path + cdef _svn.Apr_Pool tmp_pool + cdef _c_.apr_array_header_t *_c_location_revisions + cdef _svn.SvnRevnumPtrTrans revtrans + cdef _svn.CStringTransBytes transbytes + cdef _svn.HashTrans loctrans + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef object locations + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + tmp_pool = _svn.Apr_Pool(scratch_pool) + else: + tmp_pool = _svn.Apr_Pool(_svn._scratch_pool) + try: + _c_location_revisions = _svn.make_revnum_array(location_revisions, + tmp_pool._c_pool) + loctrans = _svn.HashTrans(_svn.SvnRevnumPtrTrans(), + _svn.CStringTransStr(), + tmp_pool) + serr = _c_.svn_ra_get_locations( + session._c_session, + <_c_.apr_hash_t **>(loctrans.ptr_ref()), + _c_path, _c_peg_revision, _c_location_revisions, + tmp_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + locations = loctrans.to_object() + finally: + del tmp_pool + return locations + + +# custom version of svn_ra_open*() for svn_ra.py, using auth_baton, config, +# and allocation pool from ctx +def open_session_with_ctx(object rootpath, _svn.svn_client_ctx_t ctx): + cdef const char * _c_rootpath + cdef _c_.svn_ra_callbacks2_t * _c_callbacks + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef _c_.svn_ra_session_t * _c_session + cdef svn_ra_session_t session + + # make sure rootpath is a bytes object + if isinstance(rootpath, PathLike): + rootpath = rootpath.__fspath__() + if not isinstance(rootpath, bytes) and isinstance(rootpath, str): + rootpath = rootpath.encode('utf-8') + _c_rootpath = rootpath + + assert isinstance(ctx, _svn.svn_client_ctx_t) + serr = _c_.svn_ra_create_callbacks(&_c_callbacks, ctx.pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + _c_callbacks[0].auth_baton = (ctx._c_ctx)[0].auth_baton + # we don't use any callback function, so we pass NULL as callback baton + IF SVN_API_VER >= (1, 7): + serr = _c_.svn_ra_open4( + &_c_session, NULL, _c_rootpath, NULL, _c_callbacks, NULL, + (ctx._c_ctx)[0].config, ctx.pool._c_pool) + ELIF SVN_API_VER >= (1, 5): + serr = _c_.svn_ra_open3( + &_c_session, _c_rootpath, NULL, _c_callbacks, NULL, + (ctx._c_ctx)[0].config, ctx.pool._c_pool) + ELIF SVN_API_VER >= (1, 3): + serr = _c_.svn_ra_open2( + &_c_session, _c_rootpath, _c_callbacks, NULL, + (ctx._c_ctx)[0].config, ctx.pool._c_pool) + ELSE: + # foolproof. we don't support API version below 1.3 + raise _svn.NotImplemented() + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + assert _c_session is not NULL + session = svn_ra_session_t().set_session(_c_session, ctx.pool) + return session + + +# pool free object to hold member of svn_dirent_t +# Although the C API document says svn_dirent_t is "@since New in 1.6", +# but there exists in 1.3.0 source (r858024) and used by +# svn_client_ls3() API which is available version 1.3 and above. +cdef class _Dirent(object): + cdef readonly _c_.svn_node_kind_t kind + cdef readonly _c_.svn_filesize_t size + # we don't care has_props member but hold it + cdef readonly object has_props + cdef public _c_.svn_revnum_t created_rev + # we don't care time member but hold it + cdef readonly _c_.apr_time_t time + IF PY_VERSION < (3, 0, 0): + cdef bytes last_author + ELSE: + cdef str last_author + def __init__( + self, _c_.svn_node_kind_t kind, _c_.svn_filesize_t size, + _c_.svn_boolean_t has_props, created_rev, _c_.apr_time_t time, + object last_author): + self.kind = kind + self.size = size + self.has_props = has_props + self.created_rev = created_rev + self.time = time + # assume NULL check and str/bytes conversion has been done by caller + self.last_author = last_author + + +cdef inline object _svn_dirent_to_object(const _c_.svn_dirent_t * _c_dirent): + cdef object has_props + cdef object last_author + + has_props = True if _c_dirent[0].has_props != _c_.FALSE else False + last_author = (_c_dirent[0].last_author) + IF PY_VERSION >= (3, 0, 0): + last_author = _svn._norm(last_author) + return _Dirent(_c_dirent[0].kind, _c_dirent[0].size, has_props, + _c_dirent[0].created_rev, _c_dirent[0].time, last_author) + + +IF SVN_API_VER >= (1, 4): + cdef class _list_directory_baton(object): + cdef public dict dirents + cdef public dict locks + def __cinit__(self): + self.dirents = {} + self.locks = {} + + + IF SVN_API_VER >= (1, 8): + # simple svn_client_list_func2_t implementation for list_directory() + cdef _c_.svn_error_t * _cb_list_directory( + void * _c_baton, const char * _c_path, + const _c_.svn_dirent_t * _c_dirent, + const _c_.svn_lock_t * _c_lock, + const char * _c_abs_path, const char * _c_external_parent_url, + const char * _c_external_target, + _c_.apr_pool_t * _c_scratch_pool) with gil: + cdef object btn + cdef object path + cdef _Dirent dirent + cdef _svn_repos.SvnLock lock + + btn = _c_baton + path = _c_path + IF PY_VERSION >= (3, 0, 0): + path = _svn._norm(path) + btn.dirents[path] = _svn_dirent_to_object(_c_dirent) + if _c_lock is not NULL: + btn.locks[path] = _svn_repos._svn_lock_to_object(_c_lock) + return NULL + ELSE: + # simple svn_client_list_func_t implementation for list_directory() + cdef _c_.svn_error_t * _cb_list_directory( + void * _c_baton, const char * _c_path, + const _c_.svn_dirent_t * _c_dirent, + const _c_.svn_lock_t * _c_lock, + const char * _c_abs_path, + _c_.apr_pool_t * _c_scratch_pool) with gil: + cdef _list_directory_baton btn + cdef object path + cdef _Dirent dirent + cdef _svn_repos.SvnLock lock + + btn = <_list_directory_baton>_c_baton + path = _c_path + IF PY_VERSION >= (3, 0, 0): + path = _svn._norm(path) + btn.dirents[path] = _svn_dirent_to_object(_c_dirent) + if _c_lock is not NULL: + btn.locks[path] = _svn_repos._svn_lock_to_object(_c_lock) + return NULL +ELSE: + cdef class _DirentTrans(_svn.TransPtr): + cdef const _c_.svn_dirent_t * _c_dirent + def __cinit__(self): + self._c_dirent = NULL + cdef object to_object(self): + return _svn_dirent_to_object(self._c_dirent) + cdef void set_ptr(self, void *_c_ptr): + self._c_dirent = _c_ptr + cdef void ** ptr_ref(self): + return &(self._c_dirent) + + cdef class SvnLockTrans(_svn.TransPtr): + cdef const _c_.svn_lock_t * _c_lock + def __cinit__(self): + self._c_lock = NULL + cdef object to_object(self): + return _svn_repos._svn_lock_to_object(self._c_lock) + cdef void set_ptr(self, void *_c_ptr): + self._c_lock = _c_ptr + cdef void ** ptr_ref(self): + return &(self._c_lock) + + +def list_directory( + object url, _c_.svn_revnum_t peg_rev, _c_.svn_revnum_t rev, + object recurse, _svn.svn_client_ctx_t ctx, + object scratch_pool=None): + cdef const char * _c_url + cdef _svn.svn_opt_revision_t opt_peg_rev + cdef _svn.svn_opt_revision_t opt_rev + IF SVN_API_VER >= (1, 5): + cdef _c_.svn_depth_t _c_depth + ELSE: + cdef _c_.svn_boolean_t _c_recurse + IF SVN_API_VER >= (1, 4): + cdef _list_directory_baton btn + ELSE: + cdef _svn.HashTrans dirents_trans + cdef _svn.HashTrans locks_trans + cdef dict dirents + cdef dict locks + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + # make sure url is a bytes object + if isinstance(url, PathLike): + url = url.__fspath__() + if not isinstance(url, bytes) and isinstance(url, str): + url = url.encode('utf-8') + _c_url = url + + opt_peg_rev = _svn.svn_opt_revision_t(_c_.svn_opt_revision_number, peg_rev) + opt_rev = _svn.svn_opt_revision_t(_c_.svn_opt_revision_number, rev) + IF SVN_API_VER >= (1, 4): + btn = _list_directory_baton() + ELSE: + dirents_trans = _svn.HashTrans(_svn.CstringTransBytes(), + _DirentTrans(), scratch_pool) + locks_trans = _svn.HashTrans(_svn.CstringTransBytes(), + SvnLockTrans(), scratch_pool) + IF SVN_API_VER >= (1, 5): + if recurse: + _c_depth = _c_.svn_depth_infinity + else: + _c_depth = _c_.svn_depth_immediates + ELSE: + _c_recurse = _c_.TRUE if recurse else _c_.FALSE + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create(&_c_tmp_pool, + (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + IF SVN_API_VER >= (1, 10): + serr = _c_.svn_client_list4( + _c_url, &(opt_peg_rev._c_opt_revision), + &(opt_rev._c_opt_revision), NULL, _c_depth, + _c_.SVN_DIRENT_ALL, _c_.TRUE, _c_.FALSE, + _cb_list_directory, btn, + ctx._c_ctx, _c_tmp_pool) + ELIF SVN_API_VER >= (1, 8): + serr = _c_.svn_client_list3( + _c_url, &(opt_peg_rev._c_opt_revision), + &(opt_rev._c_opt_revision), _c_depth, + _c_.SVN_DIRENT_ALL, _c_.TRUE, _c_.FALSE, + _cb_list_directory, btn, + ctx._c_ctx, _c_tmp_pool) + ELIF SVN_API_VER >= (1, 5): + serr = _c_.svn_client_list2( + _c_url, &(opt_peg_rev._c_opt_revision), + &(opt_rev._c_opt_revision), _c_depth, + _c_.SVN_DIRENT_ALL, _c_.TRUE, + _cb_list_directory, btn, + ctx._c_ctx, _c_tmp_pool) + ELIF SVN_API_VER >= (1, 4): + serr = _c_.svn_client_list( + _c_url, &(opt_peg_rev._c_opt_revision), + &(opt_rev._c_opt_revision), _c_recurse, + _c_.SVN_DIRENT_ALL, _c_.TRUE, + _cb_list_directory, btn, + ctx._c_ctx, _c_tmp_pool) + ELSE: + serr = _c_.svn_client_ls3( + <_c_.apr_hash_t **>(dirents_trans.ptr_ref()), + <_c_.apr_hash_t **> (locks_trans.ptr_ref()), + _c_url, &(opt_peg_rev._c_opt_revision), + &(opt_rev._c_opt_revision), _c_recurse, + ctx._c_ctx, _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + IF SVN_API_VER < (1, 4): + dirents = dirents_trans.to_object() + del dirents_trans + locks = locks_trans.to_object() + del locks_trans + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + IF SVN_API_VER >= (1, 4): + return btn.dirents, btn.locks + ELSE: + return dirents, locks + + +# compatibility wrapper of svn_client_cat*(), for minimum option +def svn_client_cat( + _svn.svn_stream_t out, object url, _c_.svn_revnum_t peg_rev, + _c_.svn_revnum_t rev, object expand_keywords, + object with_props, _svn.svn_client_ctx_t ctx, + object scratch_pool): + cdef const char * _c_url + cdef _svn.Apr_Pool tmp_pool + cdef _svn.svn_opt_revision_t opt_peg_rev + cdef _svn.svn_opt_revision_t opt_rev + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + IF SVN_API_VER >= (1, 9): + cdef _svn.HashTrans prop_trans + cdef _c_.apr_hash_t ** props_p + cdef _c_.svn_boolean_t _c_expand + cdef object props + + # make sure url is a bytes object + if isinstance(url, PathLike): + url = url.__fspath__() + if not isinstance(url, bytes) and isinstance(url, str): + url = url.encode('utf-8') + _c_url = url + + opt_peg_rev = _svn.svn_opt_revision_t(_c_.svn_opt_revision_number, peg_rev) + opt_rev = _svn.svn_opt_revision_t(_c_.svn_opt_revision_number, rev) + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + tmp_pool = _svn.Apr_Pool(scratch_pool) + else: + tmp_pool = _svn.Apr_Pool(_svn._scratch_pool) + + IF SVN_API_VER >= (1, 9): + if with_props: + prop_trans = _svn.HashTrans(_svn.CStringTransStr(), + _svn.SvnStringTransStr(), + tmp_pool) + props_p = <_c_.apr_hash_t **>prop_trans.ptr_ref() + else: + props_p = NULL + _c_expand = _c_.TRUE if expand_keywords else _c_.FALSE + try: + serr = _c_.svn_client_cat3( + props_p, out._c_ptr, _c_url, + &(opt_peg_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + _c_expand, ctx._c_ctx, + tmp_pool._c_pool, tmp_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + if with_props: + props = prop_trans.to_object() + else: + props = None + finally: + if with_props: + del prop_trans + del tmp_pool + ELSE: + try: + serr = _c_.svn_client_cat2( + out._c_ptr, _c_url, &(opt_peg_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + ctx._c_ctx, tmp_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + props = None + finally: + del tmp_pool + return props + + +# helper function to convert Python list of bytes to apr_array of C string +# caution: keep byteslist untill the apr_array is used. +cdef _c_.apr_array_header_t * _bytes_list_to_apr_array( + object byteslist, _c_.apr_pool_t *pool) except? NULL: + cdef _c_.apr_array_header_t * _c_arrayptr + cdef object elmstr + cdef const char * bytesptr + cdef int nelts + try: + nelts = len(byteslist) + except TypeError: + raise TypeError("Argment 'lists' must be sequence of bytes") + _c_arrayptr = _c_.apr_array_make(pool, nelts, sizeof(char *)) + if _c_arrayptr is NULL: + raise _svn.PoolError('fail to allocate memory from pool') + for elmstr in byteslist: + assert isinstance(elmstr, bytes) + # To do: make copies for each elmstr for safe. othewise, we must + # destroy the array pointed by _c_arrayptr before delete or + # modify byteslist. + bytesptr = elmstr + ((_c_.apr_array_push(_c_arrayptr)))[0] = ( + bytesptr) + return _c_arrayptr + + +cdef _c_.apr_array_header_t * _revrange_list_to_apr_array( + object range_list, _c_.apr_pool_t *pool) except? NULL: + cdef _c_.apr_array_header_t * _c_arrayptr + cdef _svn.svn_opt_revision_range_t elmrange + cdef int nelts + try: + nelts = len(range_list) + except TypeError: + raise TypeError("Argment 'lists' must be sequence of " + "svn_opt_revision_range_t") + _c_arrayptr = _c_.apr_array_make(pool, nelts, + sizeof(_c_.svn_opt_revision_range_t *)) + if _c_arrayptr is NULL: + raise _svn.PoolError('fail to allocate memory from pool') + for elmrange in range_list: + # To do: make copies for each elmrange for safe. othewise, we must + # destroy the array pointed by _c_arrayptr before delete or + # modify range_list. + ((_c_.apr_array_push(_c_arrayptr)))[0] = ( + &((<_svn.svn_opt_revision_range_t?>elmrange)._c_range)) + return _c_arrayptr + + +# call back function for svn_client_info*() used in get_last_changed_rev +IF SVN_API_VER >= (1, 7): + cdef _c_.svn_error_t * _cb_get_last_change_rev( + void * _c_baton, const char * abspath_or_url, + const _c_.svn_client_info2_t * info, + _c_.apr_pool_t * scratch_pool) nogil: + + if (<_c_.svn_revnum_t *>_c_baton)[0] != _c_.SVN_INVALID_REVNUM: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, + "_cb_get_last_changed_rev has been called " + "more than once") + return _c_err + + (<_c_.svn_revnum_t *>_c_baton)[0] = info[0].last_changed_rev + return NULL +ELSE: + cdef _cb_get_last_change_rev( + void * _c_baton, const char * path, const _c_.svn_info_t * info, + _c_.apr_pool_t * pool) nogil: + + if (<_c_.svn_revnum_t *>_c_baton)[0] != _c_.SVN_INVALID_REVNUM: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, + "_cb_get_last_changed_rev has been called " + "more than once") + return _c_err + + (<_c_.svn_revnum_t *>_c_baton)[0] = info[0].last_changed_rev + return NULL + + +# call back function for svn_client_log*() used in get_last_changed_rev +IF SVN_API_VER >= (1, 5): + # svn_log_entry_receiver_t signature + cdef _c_.svn_error_t * _cb_get_last_changed_log_rev( + void * _c_baton, _c_.svn_log_entry_t *_c_log_entry, + _c_.apr_pool_t * _c_pool) nogil: + cdef _c_.svn_error_t * _c_err + + if (<_c_.svn_revnum_t *>_c_baton)[0] != _c_.SVN_INVALID_REVNUM: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, + "_cb_get_last_changed_log_rev has been called " + "more than once") + return _c_err + + (<_c_.svn_revnum_t *>_c_baton)[0] = _c_log_entry[0].revision + return NULL +ELSE: + # svn_log_message_receiver_t signature + cdef _c_.svn_error_t * _cb_get_last_changed_log_rev( + void * _c_baton, _c_.apr_hash_t * _c_changed_paths, + _c_.svn_revnum_t _c_revision, const char * _c_author, + const char * _c_date, const char * _c_message, + _c_.apr_pool_t * pool) nogil: + cdef _c_.svn_error_t * _c_err + + if (<_c_.svn_revnum_t *>_c_baton)[0] != _c_.SVN_INVALID_REVNUM: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, + "_cb_get_last_changed_log_rev has been called " + "more than once") + return _c_err + + (<_c_.svn_revnum_t *>_c_baton)[0] = _c_revision + return NULL + +def get_last_history_rev( + object url, _c_.svn_revnum_t rev, _svn.svn_client_ctx_t ctx, + object scratch_pool=None): + cdef const char * _c_url + cdef _svn.Apr_Pool tmp_pool + cdef _svn.svn_opt_revision_t opt_rev + cdef _c_.svn_revnum_t lcrev + cdef _svn.svn_opt_revision_t opt_lc_rev + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef list targets + cdef _c_.apr_array_header_t * _c_targets + cdef list rev_ranges + IF SVN_API_VER >= (1, 6): + cdef _c_.apr_array_header_t * _c_rev_ranges + cdef _c_.apr_array_header_t * _c_revprops + cdef _c_.svn_revnum_t lhrev + + # make sure url is a bytes object + if isinstance(url, PathLike): + url = url.__fspath__() + if not isinstance(url, bytes) and isinstance(url, str): + url = url.encode('utf-8') + _c_url = url + + opt_rev = _svn.svn_opt_revision_t(_c_.svn_opt_revision_number, rev) + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + tmp_pool = _svn.Apr_Pool(scratch_pool) + else: + tmp_pool = _svn.Apr_Pool(_svn._scratch_pool) + lcrev = _c_.SVN_INVALID_REVNUM + lhrev = _c_.SVN_INVALID_REVNUM + _c_targets = NULL + try: + IF SVN_API_VER >= (1, 9): + serr = _c_.svn_client_info4( + _c_url, &(opt_rev._c_opt_revision), + &(opt_rev._c_opt_revision), _c_.svn_depth_empty, + _c_.FALSE, _c_.TRUE, _c_.FALSE, NULL, + _cb_get_last_change_rev, &lcrev, + ctx._c_ctx, tmp_pool._c_pool) + ELIF SVN_API_VER >= (1, 7): + serr = _c_.svn_client_info3( + _c_url, &(opt_rev._c_opt_revision), + &(opt_rev._c_opt_revision), _c_.svn_depth_empty, + _c_.FALSE, _c_.TRUE, NULL, + _cb_get_last_change_rev, &lcrev, + ctx._c_ctx, tmp_pool._c_pool) + ELIF SVN_API_VER >= (1, 5): + serr = _c_.svn_client_info2( + _c_url, &(opt_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + _cb_get_last_change_rev, &lcrev, + _c_.svn_depth_empty, NULL, + ctx._c_ctx, tmp_pool._c_pool) + ELSE: + serr = _c_.svn_client_info( + _c_url, &(opt_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + _cb_get_last_change_rev, &lcrev, + _c_.FALSE, ctx._c_ctx, tmp_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + # now we can get lastest revision that the object was modified its + # text or properties, however it might have been modified the + # path (i.e. copied from somewhere.) To detect such the latest + # action and its revision only, call back of LogCollector are + # too complex. So we can use simple call back that hold revision. + opt_lc_rev = _svn.svn_opt_revision_t(_c_.svn_opt_revision_number, + lcrev) + targets = [url] + _c_targets = _bytes_list_to_apr_array(targets, tmp_pool._c_pool) + IF SVN_API_VER >= (1, 4): + # we don't need any revision props. + _c_rev_props = _c_.apr_array_make(tmp_pool._c_pool, 0, + sizeof(const char *)) + IF SVN_API_VER >= (1, 6): + rev_ranges = [_svn.svn_opt_revision_range_t(opt_rev, opt_lc_rev)] + _c_rev_ranges = _revrange_list_to_apr_array( + rev_ranges, tmp_pool._c_pool) + serr = _c_.svn_client_log5( + _c_targets, &(opt_rev._c_opt_revision), + _c_rev_ranges, 1, _c_.FALSE, _c_.TRUE, + _c_.FALSE, _c_rev_props, + _cb_get_last_changed_log_rev, &lhrev, + ctx._c_ctx, tmp_pool._c_pool) + ELIF SVN_API_VER >= (1, 5): + serr = _c_.svn_client_log4( + _c_targets, &(opt_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + &(opt_lc_rev._c_opt_revision), + 1, _c_.FALSE, _c_.TRUE, + _c_.FALSE, _c_rev_props, + _cb_get_last_changed_log_rev, &lhrev, + ctx._c_ctx, tmp_pool._c_pool) + ELIF SVN_API_VER >= (1, 4): + serr = _c_.svn_client_log3( + _c_targets, &(opt_rev._c_opt_revision), + &(opt_rev._c_opt_revision), + &(opt_lc_rev._c_opt_revision), + 1, _c_.FALSE, _c_.TRUE, + _cb_get_last_changed_log_rev, &lhrev, + ctx._c_ctx, tmp_pool._c_pool) + ELSE: + serr = _c_.svn_client_log2( + _c_targets, + &(opt_rev._c_opt_revision), + &(opt_lc_rev._c_opt_revision), + 1, _c_.FALSE, _c_.TRUE, + _cb_get_last_changed_log_rev, &lhrev, + ctx._c_ctx, tmp_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + IF SVN_API_VER >= (1, 6): + if _c_rev_ranges is not NULL: + _c_.apr_array_clear(_c_rev_ranges) + _c_rev_ranges = NULL + IF SVN_API_VER >= (1, 5): + if _c_rev_props is not NULL: + _c_.apr_array_clear(_c_rev_props) + _c_rev_props = NULL + if _c_targets is not NULL: + _c_.apr_array_clear(_c_targets) + _c_targets = NULL + del tmp_pool + if lhrev != _c_.SVN_INVALID_REVNUM: + return lhrev, lcrev + else: + return lcrev, lcrev + + +# apr_pool free data reference objects for svn_log_entry_t +IF SVN_API_VER >= (1, 6): + cdef class py_svn_log_changed_path2_ref(object): + def __cinit__(self): + self.action = None + self.copyfrom_path = None + self.copyfrom_rev = None + self.node_kind = _c_.svn_node_unknown + IF SVN_API_VER >= (1, 7): + self.text_modified = _c_.svn_tristate_unknown + self.props_modified = _c_.svn_tristate_unknown + + cdef py_svn_log_changed_path2_ref bind( + py_svn_log_changed_path2_ref self, + const _c_.svn_log_changed_path2_t * ptr): + assert ptr is not NULL + self.action = chr(ptr[0].action) + if ptr[0].copyfrom_path is NULL: + self.copyfrom_path = None + self.copyfrom_rev = None + else: + self.copyfrom_path = (ptr[0].copyfrom_path) + IF PY_VERSION >= (3, 0, 0): + self.copyfrom_path = _svn._norm(self.copyfrom_path) + self.copyfrom_rev = ptr[0].copyfrom_rev + self.node_kind = ptr[0].node_kind + IF SVN_API_VER >= (1, 7): + self.text_modified = ptr[0].text_modified + self.props_modified = ptr[0].props_modified + return self + + cdef class SvnLogChangedPath2Trans(_svn.TransPtr): + cdef _c_.svn_log_changed_path2_t * _c_ptr + cdef void ** ptr_ref(self): + return &(self._c_ptr) + cdef void set_changed_path2( + self, _c_.svn_log_changed_path2_t * _c_ptr): + self._c_ptr = _c_ptr + cdef object to_object(self): + cdef py_svn_log_changed_path2_ref changed_path + changed_path = py_svn_log_changed_path2_ref() + changed_path.bind(self._c_ptr) + return changed_path + + +cdef class py_svn_log_changed_path_ref(object): + def __cinit__(self): + self.action = None + self.copyfrom_path = None + self.copyfrom_rev = None + + cdef py_svn_log_changed_path_ref bind( + py_svn_log_changed_path_ref self, + const _c_.svn_log_changed_path_t * ptr): + assert ptr is not NULL + self.action = chr(ptr[0].action) + if ptr[0].copyfrom_path is NULL: + self.copyfrom_path = None + self.copyfrom_rev = None + else: + self.copyfrom_path = (ptr[0].copyfrom_path) + IF PY_VERSION >= (3, 0, 0): + self.copyfrom_path = _svn._norm(self.copyfrom_path) + self.copyfrom_rev = ptr[0].copyfrom_rev + return self + +cdef class SvnLogChangedPathTrans(_svn.TransPtr): + cdef _c_.svn_log_changed_path_t * _c_ptr + cdef void ** ptr_ref(self): + return &(self._c_ptr) + cdef void set_changed_path(self, _c_.svn_log_changed_path_t * _c_ptr): + self._c_ptr = _c_ptr + cdef object to_object(self): + cdef py_svn_log_changed_path_ref changed_path + changed_path = py_svn_log_changed_path_ref() + changed_path.bind(self._c_ptr) + return changed_path + + +# apr_pool free data reference object for svn_log_entry_t +cdef class py_svn_log_entry(object): + def __cinit__(self): + self.changed_paths = None + self.revision = _c_.SVN_INVALID_REVNUM + self.revprops = None + IF SVN_API_VER >= (1, 5): + self.has_children = False + IF SVN_API_VER >= (1, 6): + self.changed_paths2 = None + IF SVN_API_VER >= (1, 7): + self.non_inheritable = False + self.subtractive_merge = False + return + + IF SVN_API_VER >= (1, 5): + cdef void bind(self, const _c_.svn_log_entry_t *_c_ptr, + _svn.Apr_Pool scratch_pool): + cdef _svn.Apr_Pool tmp_pool + cdef _svn.HashTrans cp_trans + cdef _svn.HashTrans prop_trans + IF SVN_API_VER >= (1, 6): + cdef _svn.HashTrans cp2_trans + + assert _c_ptr is not NULL + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + tmp_pool = _svn.Apr_Pool(scratch_pool) + if _c_ptr[0].changed_paths is NULL: + self.changed_paths = {} + else: + cp_trans = _svn.HashTrans(_svn.CStringTransStr(), + SvnLogChangedPathTrans(), + tmp_pool) + try: + cp_trans.set_ptr((_c_ptr[0].changed_paths)) + self.changed_paths = cp_trans.to_object() + finally: + del cp_trans + self.revision = _c_ptr[0].revision + if _c_ptr[0].revprops is NULL: + self.revprops = {} + else: + prop_trans = _svn.HashTrans(_svn.CStringTransStr(), + _svn.SvnStringTransStr(), + tmp_pool) + try: + prop_trans.set_ptr((_c_ptr[0].revprops)) + self.revprops = prop_trans.to_object() + finally: + del prop_trans + if _c_ptr[0].has_children: + self.has_children = True + else: + self.has_children = False + IF SVN_API_VER >= (1, 6): + if _c_ptr[0].changed_paths2 is NULL: + self.changed_paths2 = {} + else: + cp2_trans = _svn.HashTrans( + _svn.CStringTransStr(), + SvnLogChangedPath2Trans(), + tmp_pool) + try: + cp2_trans.set_ptr((_c_ptr[0].changed_paths2)) + self.changed_paths2 = cp2_trans.to_object() + finally: + del cp2_trans + IF SVN_API_VER >= (1, 7): + if _c_ptr[0].non_inheritable: + self.non_inheritable = True + else: + self.non_inheritable = False + if _c_ptr[0].subtractive_merge: + self.subtractive_merge = True + else: + self.subtractive_merge = False + del tmp_pool + return + ELSE: + cdef void bind( + self, const _c_.apr_hash_t *_c_changed_paths, + _c_.svn_revision_t _c_revision, const char * author, + const char * date, const char * message, + _svn.Apr_Pool scratch_pool): + cdef _svn.Apr_Pool tmp_pool + cdef _svn.HashTrans cp_trans + + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + tmp_pool = Apr_Pool(scratch_pool) + if _c_ptr[0].changed_paths is NULL: + self.changed_paths = {} + else: + cp_trans = _svn.HashTrans(_svn.CStringTransStr(), + _svn.SvnLogChangedPathTrans(), + tmp_pool) + try: + cp_trans.set_ptr((_c_changed_paths)) + self.changed_paths = cp_trans.to_object() + finally: + del cp_trans + self.revision = _c_.revision + IF PY_VERSION >= (3, 0, 0): + self.revprops = { + _svn.SVN_PROP_REVISION_LOG : + (_svn._norm(_c_message) + if _c_message is not NULL else ''), + _svn.SVN_PROP_REVISION_AUTHOR : + (_svn._norm(_c_author) + if _c_author is not NULL else ''), + _svn.SVN_PROP_REVISION_DATE : + (_svn._norm(_c_date) + if _c_date is not NULL else '')} + ELSE: + self.revprops = { + _svn.SVN_PROP_REVISION_LOG : + (_c_message + if _c_message is not NULL else ''), + _svn.SVN_PROP_REVISION_AUTHOR : + (_c_author + if _c_author is not NULL else ''), + _svn.SVN_PROP_REVISION_DATE : + (_c_date + if _c_date is not NULL else '') } + del tmp_pool + return + +# call back function for svn_client_log*() +IF SVN_API_VER >= (1, 5): + cdef _c_.svn_error_t * _cb_svn_log_entry_receiver_t_wrapper( + void * _c_baton, _c_.svn_log_entry_t * _c_log_entry, + _c_.apr_pool_t * _c_pool) with gil: + cdef _svn.CbContainer btn + cdef py_svn_log_entry log_entry + cdef _c_.svn_error_t * _c_err + cdef _svn.Svn_error svnerr + + btn = <_svn.CbContainer>_c_baton + log_entry = py_svn_log_entry() + _c_err = NULL + try: + log_entry.bind(_c_log_entry, btn.pool) + btn.fnobj(btn.btn, log_entry, btn.pool) + except _svn.SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except AssertionError, err: + IF PY_VERSION >= (3, 0, 0): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, + str(err).encode('utf-8')) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, str(err)) + except KeyboardInterrupt, err: + IF PY_VERSION >= (3, 0, 0): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, + str(err).encode('utf-8')) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + except BaseException, err: + IF PY_VERSION >= (3, 0, 0): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_BASE, NULL, str(err).encode('utf-8')) + ELSE: + _c_err = _c_.svn_error_create(_c_.SVN_ERR_BASE, NULL, str(err)) + return _c_err +ELSE: + cdef _c_.svn_error_t * _cb_svn_log_message_receiver_t_wrapper( + void * _c_baton, _c_.apr_hash_t * _c_changed_paths, + _c_.svn_revnum_t _c_revision, const char * _c_author, + const char * _c_date, const char * _c_message, + _c_.apr_pool_t * pool) with gil: + cdef _svn.CbContainer btn + cdef py_svn_log_entry log_entry + cdef _c_.svn_error_t * _c_err + cdef _svn.Svn_error svnerr + + btn = <_svn.CbContainer>_c_baton + log_entry = py_svn_log_entry() + _c_err = NULL + try: + log_entry.bind( + _c_changed_paths, _c_revision, _c_author, _c_date, + _c_message, btn.pool) + btn.fnobj(btn.btn, log_entry, btn.pool) + except _svn.SVNerr as serr: + svnerr = serr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del serr + except AssertionError, err: + IF PY_VERSION >= (3, 0, 0): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, + str(err).encode('utf-8')) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, str(err)) + except KeyboardInterrupt, err: + IF PY_VERSION >= (3, 0, 0): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, + str(err).encode('utf-8')) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + except BaseException, err: + IF PY_VERSION >= (3, 0, 0): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_BASE, NULL, str(err).encode('utf-8')) + ELSE: + _c_err = _c_.svn_error_create(_c_.SVN_ERR_BASE, NULL, str(err)) + return _c_err + + +# svn_client_log*() wrapper for vclib: disable some feature unused +def client_log( + object url, object start_rev, object end_rev, int log_limit, + object include_changes, object cross_copies, object cb_func, + object baton, _svn.svn_client_ctx_t ctx, object scratch_pool=None): + cdef _svn.Apr_Pool tmp_pool + cdef _svn.svn_opt_revision_t opt_start_rev + cdef _svn.svn_opt_revision_t opt_end_rev + cdef _c_.svn_boolean_t _c_discover_changed_paths + cdef _c_.svn_boolean_t _c_strict_node_history + cdef object targets + cdef _c_.apr_array_header_t * _c_targets + cdef _svn.CbContainer btn + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + IF SVN_API_VER >= (1, 6): + cdef list rev_ranges + cdef _c_.apr_array_header_t * _c_rev_ranges + + # make sure url is a bytes object + if isinstance(url, PathLike): + url = url.__fspath__() + if not isinstance(url, bytes) and isinstance(url, str): + url = url.encode('utf-8') + + if isinstance(start_rev, _svn.svn_opt_revision_t): + opt_start_rev = start_rev + else: + opt_start_rev = _svn.svn_opt_revision_t( + _c_.svn_opt_revision_number, start_rev) + if isinstance(end_rev, _svn.svn_opt_revision_t): + opt_end_rev = end_rev + else: + opt_end_rev = _svn.svn_opt_revision_t( + _c_.svn_opt_revision_number, end_rev) + _c_discover_changed_paths = _c_.TRUE if include_changes else _c_.FALSE + _c_strict_node_history = _c_.TRUE if not cross_copies else _c_.FALSE + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + tmp_pool = _svn.Apr_Pool(scratch_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + tmp_pool = _svn.Apr_Pool(_svn._scratch_pool) + try: + targets = [url] + _c_targets = _bytes_list_to_apr_array(targets, tmp_pool._c_pool) + btn = _svn.CbContainer(cb_func, baton, tmp_pool) + IF SVN_API_VER >= (1, 6): + rev_ranges = [_svn.svn_opt_revision_range_t(opt_start_rev, + opt_end_rev)] + _c_rev_ranges = _revrange_list_to_apr_array( + rev_ranges, tmp_pool._c_pool) + serr = _c_.svn_client_log5( + _c_targets, &(opt_start_rev._c_opt_revision), + _c_rev_ranges, log_limit, _c_discover_changed_paths, + _c_strict_node_history, _c_.FALSE, NULL, + _cb_svn_log_entry_receiver_t_wrapper,btn, + ctx._c_ctx, tmp_pool._c_pool) + ELIF SVN_API_VER >= (1, 5): + serr = _c_.svn_client_log4( + _c_targets, &(opt_start_rev._c_opt_revision), + &(opt_start_rev._c_opt_revision), + &(opt_end_rev._c_opt_revision), + log_limit, _c_discover_changed_paths, + _c_strict_node_history, _c_.FALSE, NULL, + _cb_svn_log_entry_receiver_t_wrapper,btn, + ctx._c_ctx, tmp_pool._c_pool) + ELIF SVN_API_VER >= (1, 4): + serr = _c_.svn_client_log3( + _c_targets, &(opt_start_rev._c_opt_revision), + &(opt_start_rev._c_opt_revision), + &(opt_end_rev._c_opt_revision), + log_limit, _c_discover_changed_paths, + _c_strict_node_history, + _cb_svn_log_message_receiver_t_wrapper, btn, + ctx._c_ctx, tmp_pool._c_pool) + ELSE: + serr = _c_.svn_client_log2( + _c_targets, + &(opt_start_rev._c_opt_revision), + &(opt_end_rev._c_opt_revision), + log_limit, _c_discover_changed_paths, + _c_strict_node_history, + _cb_svn_log_message_receiver_t_wrapper, btn, + ctx._c_ctx, tmp_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + IF SVN_API_VER >= (1, 6): + if _c_rev_ranges is not NULL: + _c_.apr_array_clear(_c_rev_ranges) + _c_rev_ranges = NULL + if _c_targets is not NULL: + _c_.apr_array_clear(_c_targets) + _c_targets = NULL + del tmp_pool + return + +# simplified svn_client_prolist*() API for single node and single revision +# without inherited props +IF SVN_API_VER >= (1, 5): + cdef _c_.svn_error_t * _cb_simple_proplist_body( + void *_c_baton, _c_.apr_hash_t * _c_prop_hash, + _c_.apr_pool_t * _c_scratch_pool) with gil: + cdef object baton + cdef _c_.svn_error_t * _c_err + cdef _svn.Svn_error pyerr + cdef _svn.Apr_Pool scratch_pool + cdef _svn.Apr_Pool tmp_pool + cdef _svn.HashTrans prop_trans + cdef object propdict + + baton = _c_baton + if baton: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, + "_cb_simple_proplist_receiver has been called " + "more than once") + return _c_err + _c_err = NULL + if _c_prop_hash is NULL: + baton.append({}) + else: + scratch_pool = _svn.Apr_Pool.__new__(_svn.Apr_Pool, pool=None) + scratch_pool.set_pool(_c_scratch_pool) + tmp_pool = _svn.Apr_Pool(scratch_pool) + try: + prop_trans = _svn.HashTrans(_svn.CStringTransStr(), + _svn.SvnStringTransStr(), + tmp_pool) + prop_trans.set_ptr((_c_prop_hash)) + propdict = prop_trans.to_object() + del prop_trans + baton.append(propdict) + except _svn.SVNerr as serr: + pyerr = serr.svnerr + _c_err = _c_.svn_error_dup(pyerr.geterror()) + del serr + except AssertionError, err: + IF PY_VERSION >= (3, 0, 0): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, + str(err).encode('utf-8')) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, str(err)) + except KeyboardInterrupt, err: + IF PY_VERSION >= (3, 0, 0): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, + str(err).encode('utf-8')) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + except BaseException, err: + IF PY_VERSION >= (3, 0, 0): + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_BASE, NULL, + str(err).encode('utf-8')) + ELSE: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_BASE, NULL, str(err)) + finally: + del tmp_pool + del scratch_pool + return _c_err + + + IF SVN_API_VER >= (1, 8): + cdef _c_.svn_error_t * _cb_simple_proplist_receiver( + void * _c_baton, const char * _c_path, + _c_.apr_hash_t * _c_prop_hash, + _c_.apr_array_header_t * inherited_props, + _c_.apr_pool_t * _c_scratch_pool) nogil: + return _cb_simple_proplist_body( + _c_baton, _c_prop_hash, _c_scratch_pool) + ELSE: + cdef _c_.svn_error_t * _cb_simple_proplist_receiver( + void * _c_baton, const char * _c_path, + _c_.apr_hash_t * _c_prop_hash, + _c_.apr_pool_t * _c_scratch_pool) nogil: + return _cb_simple_proplist_body( + _c_baton, _c_prop_hash, _c_scratch_pool) + +def simple_proplist( + object url, object rev, _svn.svn_client_ctx_t ctx, + object scratch_pool=None): + cdef const char * _c_url + cdef _svn.Apr_Pool tmp_pool + cdef _svn.svn_opt_revision_t opt_rev + IF SVN_API_VER >= (1, 5): + cdef object propdic_list + ELSE: + cdef _c_.apr_array_header_t * _c_props + cdef _c_.svn_client_proplist_item_t * _c_prop_item + cdef _svn.HashTrans prop_trans + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef object propdic + + # make sure url is a bytes object + if isinstance(url, PathLike): + url = url.__fspath__() + if not isinstance(url, bytes) and isinstance(url, str): + url = url.encode('utf-8') + _c_url = url + + if isinstance(rev, _svn.svn_opt_revision_t): + opt_rev = rev + else: + opt_rev = _svn.svn_opt_revision_t(_c_.svn_opt_revision_number, rev) + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + tmp_pool = _svn.Apr_Pool(scratch_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + tmp_pool = _svn.Apr_Pool(_svn._scratch_pool) + IF SVN_API_VER >= (1, 5): + propdic_list = [] + try: + IF SVN_API_VER >= (1, 8): + serr = _c_.svn_client_proplist4( + _c_url, &(opt_rev._c_opt_revision), + &(opt_rev._c_opt_revision), _c_.svn_depth_empty, + NULL, _c_.FALSE, + _cb_simple_proplist_receiver, propdic_list, + ctx._c_ctx, tmp_pool._c_pool) + ELIF SVN_API_VER >= (1, 5): + serr = _c_.svn_client_proplist3( + _c_url, &(opt_rev._c_opt_revision), + &(opt_rev._c_opt_revision), _c_.svn_depth_empty, NULL, + _cb_simple_proplist_receiver, propdic_list, + ctx._c_ctx, tmp_pool._c_pool) + ELSE: + _c_props = NULL + serr = _c_.svn_client_proplist2( + &_c_props, _c_url, &(opt_rev._c_opt_revision), + &(opt_rev._c_opt_revision), _c_.FALSE, + ctx._c_ctx, tmp_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + IF SVN_API_VER > (1, 5): + # extract props from array + if propdic_list: + assert len(propdic_list) == 1 + propdic = propdic_list[0] + else: + propdic = {} + ELSE: + # extract props from array + assert _c_props is not NULL + assert _c_props[0].nels == 1 + prop_trans = _svn.HashTrans(_svn.CStringTransStr(), + _svn.SvnStringTransStr(), + tmp_pool) + if _c_props[0].elts is NULL: + propdic = {} + else: + (<_c_.apr_hash_t **>(prop_trans.ptr_ref()))[0] = \ + <_c_.apr_hash_t *>(_c_props[0].elts) + propdic = prop_trans.to_object() + del prop_trans + finally: + del tmp_pool + return propdic diff --git a/src/lib/vclib/altsvn/_svn_ra_capi.pxd b/src/lib/vclib/altsvn/_svn_ra_capi.pxd new file mode 100644 index 00000000..229c5cab --- /dev/null +++ b/src/lib/vclib/altsvn/_svn_ra_capi.pxd @@ -0,0 +1,12 @@ +include "_svn_api_ver.pxi" +from apr_1.apr cimport * +from apr_1.apr_errno cimport * +from apr_1.apr_pools cimport * +from apr_1.apr_hash cimport * +from apr_1.apr_tables cimport * +from subversion_1.svn_types cimport * +from subversion_1.svn_error cimport * +from subversion_1.svn_error_codes cimport * +from subversion_1.svn_opt cimport * +from subversion_1.svn_client cimport * +from subversion_1.svn_ra cimport * diff --git a/src/lib/vclib/altsvn/_svn_repos.pxd b/src/lib/vclib/altsvn/_svn_repos.pxd new file mode 100644 index 00000000..3087f040 --- /dev/null +++ b/src/lib/vclib/altsvn/_svn_repos.pxd @@ -0,0 +1,109 @@ +include "_py_ver.pxi" +include "_svn_api_ver.pxi" +from cpython.ref cimport PyObject +cimport _svn_repos_capi as _c_ +cimport _svn + +cdef class svn_fs_t(object): + cdef _c_.svn_fs_t * _c_ptr + cdef dict roots + cdef _svn.Apr_Pool pool + cdef set_fs(self, _c_.svn_fs_t * fs, object pool) + +cdef class svn_fs_root_t(object): + cdef _c_.svn_fs_root_t * _c_ptr + cdef _svn.Apr_Pool pool + cdef set_fs_root(self, _c_.svn_fs_root_t * fs_root, object pool) + +cdef class svn_fs_id_t(object): + cdef _c_.svn_fs_id_t * _c_ptr + cdef _svn.Apr_Pool pool + cdef set_fs_id(self, _c_.svn_fs_id_t * fs_id, object pool) + +cdef class svn_fs_history_t(object): + cdef _c_.svn_fs_history_t * _c_ptr + cdef _svn.Apr_Pool pool + cdef set_history(self, _c_.svn_fs_history_t * history, object pool) + +ctypedef _c_.svn_error_t * (* svn_rv1_root_path_func_t)( + void ** _c_r_ptr, _c_.svn_fs_root_t *_c_root, + const char *_c_path, _c_.apr_pool_t * _c_pool) nogil + +IF SVN_API_VER < (1, 10): + cdef class FsPathChangeTrans(_svn.TransPtr): + IF SVN_API_VER >= (1, 6): + cdef _c_.svn_fs_path_change2_t * _c_change + ELSE: + cdef _c_.svn_fs_path_change_t * _c_change + cdef _svn.Apr_Pool result_pool + cdef _svn.Apr_Pool tmp_pool + cdef object to_object(self) + IF SVN_API_VER >= (1, 6): + cdef void set_c_change( + self, _c_.svn_fs_path_change2_t * _c_change, + object result_pool) + ELSE: + cdef void set_c_change( + self, _c_.svn_fs_path_change_t * _c_change, + object result_pool) + cdef void ** ptr_ref(self) + +cdef class NodeKindTrans(_svn.TransPtr): + cdef _c_.svn_node_kind_t _c_kind + cdef object to_object(self) + cdef void set_c_kind(self, _c_.svn_node_kind_t _c_kind) + cdef void ** ptr_ref(self) + +cdef class FileSizeTrans(_svn.TransPtr): + cdef _c_.svn_filesize_t _c_fsize + cdef object to_object(self) + cdef void set_filesize(self, _c_.svn_filesize_t _c_fsize) + cdef void ** ptr_ref(self) + +cdef class svn_repos_t(object): + cdef _c_.svn_repos_t * _c_ptr + cdef _svn.Apr_Pool pool + cdef svn_repos_t set_repos( + svn_repos_t self, _c_.svn_repos_t * repos, object pool) + +cdef class SvnLock(object): + cdef public object path + cdef public object token + cdef public object owner + cdef public object comment + cdef public object is_dav_comment + cdef public _c_.apr_time_t creation_date + cdef public _c_.apr_time_t expiration_date + +cdef object _svn_lock_to_object(const _c_.svn_lock_t * _c_lock) + +cdef class _ChangedPath(object): + cdef public _c_.svn_node_kind_t item_kind + cdef public _c_.svn_boolean_t prop_changes + cdef public _c_.svn_boolean_t text_changed + IF PY_VERSION >= (3, 0, 0): + cdef public str base_path + ELSE: + cdef public bytes base_path + cdef public _c_.svn_revnum_t base_rev + IF PY_VERSION >= (3, 0, 0): + cdef public str path + ELSE: + cdef public bytes path + cdef public _c_.svn_boolean_t added + ### we don't use 'None' action + cdef public _c_.svn_fs_path_change_kind_t action + +cdef class _get_changed_paths_EditBaton(object): + cdef dict changes + cdef svn_fs_t fs_ptr + cdef svn_fs_root_t root + cdef _c_.svn_revnum_t base_rev + # pool for path in _get_changed_paths_DirBaton + cdef _c_.apr_pool_t * _c_p_pool + +ctypedef struct _get_changed_paths_DirBaton: + const char * path + const char * base_path + _c_.svn_revnum_t base_rev + void * edit_baton diff --git a/src/lib/vclib/altsvn/_svn_repos.pyx b/src/lib/vclib/altsvn/_svn_repos.pyx new file mode 100644 index 00000000..8fda3fe6 --- /dev/null +++ b/src/lib/vclib/altsvn/_svn_repos.pyx @@ -0,0 +1,1680 @@ +include "_svn_api_ver.pxi" +include "_py_ver.pxi" +from libc.string cimport memcpy +cimport _svn_repos_capi as _c_ +cimport _svn +from . import _svn + +try: + import os + PathLike = os.PathLike +except AttributeError: + class PathLike(object): + pass + +cdef class svn_fs_t(object): + # cdef _c_.svn_fs_t * _c_ptr + # cdef dict roots + # cdef _svn.Apr_Pool pool + def __cinit__(self, **m): + self._c_ptr = NULL + self.roots = {} + self.pool = None + cdef set_fs(self, _c_.svn_fs_t * fs, pool): + self._c_ptr = fs + self.roots = {} + assert pool is None or isinstance(pool, _svn.Apr_Pool) + self.pool = pool + return self + def _getroot(self, rev): + try: + return self.roots[rev] + except KeyError: + pass + root = self.roots[rev] = svn_fs_revision_root(self, rev, self.pool) + return root + +cdef class svn_fs_root_t(object): + # cdef _c_.svn_fs_root_t * _c_ptr + # cdef _svn.Apr_Pool + def __cinit__(self): + self._c_ptr = NULL + self.pool = None + cdef set_fs_root(self, _c_.svn_fs_root_t * fs_root, pool): + self._c_ptr = fs_root + assert pool is None or isinstance(pool, _svn.Apr_Pool) + self.pool = pool + return self + +cdef class svn_fs_id_t(object): + # cdef _c_.svn_fs_id_t * _c_ptr + # cdef _svn.Apr_Pool pool + def __cinit__(self): + self._c_ptr = NULL + cdef set_fs_id(self, _c_.svn_fs_id_t * fs_id, pool): + self._c_ptr = fs_id + assert pool is None or isinstance(pool, _svn.Apr_Pool) + self.pool = pool + return self + +def svn_fs_compare_ids(svn_fs_id_t a, svn_fs_id_t b): + return _c_.svn_fs_compare_ids(a._c_ptr, b._c_ptr) + + +# warn: though pool is optional, ommiting to specify it causes +# allocation from global pool, and not releases its allocation until +# the program terminates. +def svn_fs_revision_root(svn_fs_t fs, _c_.svn_revnum_t rev, pool=None): + cdef _svn.Apr_Pool r_pool + cdef _c_.svn_fs_root_t * _c_root + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef svn_fs_root_t root + + if pool is not None: + assert (<_svn.Apr_Pool?>pool)._c_pool is not NULL + r_pool = pool + else: + r_pool = _svn._root_pool + serr = _c_.svn_fs_revision_root( + &_c_root, fs._c_ptr, rev, r_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + root = svn_fs_root_t().set_fs_root(_c_root, r_pool) + return root + +cdef object _apply_svn_api_root_path_arg1( + svn_rv1_root_path_func_t svn_api, _svn.TransPtr rv_trans, + svn_fs_root_t root, object path, scratch_pool=None): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef const char * _c_path + cdef object rv + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create(&_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + serr = svn_api(rv_trans.ptr_ref(), root._c_ptr, _c_path, _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + rv = rv_trans.to_object() + except: + rv = None + raise + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return rv + +# export svn C API constants into Python object +svn_fs_path_change_modify = _c_.svn_fs_path_change_modify +svn_fs_path_change_add = _c_.svn_fs_path_change_add +svn_fs_path_change_delete = _c_.svn_fs_path_change_delete +svn_fs_path_change_replace = _c_.svn_fs_path_change_replace +svn_fs_path_change_reset = _c_.svn_fs_path_change_reset + +# This class is a placeholder of contents of svn_fs_path_change_t, +# svn_fs_path_change2_t, svn_fs_path_change3_t enough to the extent +# to use from svn_repos.py[x], but not provide full function. +class FsPathChange(object): + IF SVN_API_VER >= (1, 10): + # svn_fs_path_change3_t + def __init__(self, change_kind, node_kind, text_mod, prop_mod, + mergeinfo_mod, copyfrom_known, copyfrom_path): + self.change_kind = change_kind + self.node_kind = node_kind + self.text_mod = text_mod + self.prop_mod = prop_mod + self.mergeinfo_mod = mergeinfo_mod + self.copyfrom_known = copyfrom_known + self.copyfrom_path = copyfrom_path + ELIF SVN_API_VER >= (1, 9): + # svn_fs_path_change2_t + def __init__(self, node_rev_id, change_kind, text_mod, prop_mod, + node_kind, copyfrom_known, copyfrom_rev, copyfrom_path, + mergeinfo_mod = _c_.svn_tristate_unknown): + self.node_rev_id = node_rev_id + self.change_kind = change_kind + self.text_mod = text_mod + self.prop_mod = prop_mod + self.node_kind = node_kind + self.copyfrom_known = copyfrom_known + self.copyfrom_rev = copyfrom_rev + self.copyfrom_path = copyfrom_path + self.mergeinfo_mod = mergeinfo_mod + ELIF SVN_API_VER >= (1, 6): + def __init__(self, node_rev_id, change_kind, text_mod, prop_mod, + node_kind, copyfrom_known, copyfrom_rev, copyfrom_path): + self.node_rev_id = node_rev_id + self.change_kind = change_kind + self.text_mod = text_mod + self.prop_mod = prop_mod + self.node_kind = node_kind + self.copyfrom_known = copyfrom_known + self.copyfrom_rev = copyfrom_rev + self.copyfrom_path = copyfrom_path + ELSE: + def __init__(self, node_rev_id, change_kind, text_mod, prop_mod): + self.node_rev_id = node_rev_id + self.change_kind = change_kind + self.text_mod = text_mod + self.prop_mod = prop_mod + +IF SVN_API_VER < (1, 10): + cdef class FsPathChangeTrans(_svn.TransPtr): + def __cinit__(self, result_pool=None, scratch_pool=None, **m): + self.result_pool = None + self.tmp_pool = None + def __init__(self, result_pool=None, scratch_pool=None, **m): + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + self.tmp_pool = _svn.Apr_Pool(scratch_pool) + else: + self.tmp_pool = _svn.Apr_Pool(_svn.root_pool) + if result_pool is not None: + assert (<_svn.Apr_Pool?>result_pool)._c_pool is not NULL + self.result_pool = result_pool + else: + self.result_pool = _svn._root_pool + cdef object to_object(self): + IF SVN_API_VER >= (1, 6): + cdef object copyfrom_path + if (self._c_change)[0].copyfrom_path is NULL: + copyfrom_path = None + else: + copyfrom_path = ((self._c_change)[0].copyfrom_path) + IF PY_VERSION >= (3, 0, 0): + copyfrom_path = _svn._norm(copyfrom_path) + IF SVN_API_VER == (1, 9): + return FsPathChange( + svn_fs_id_t().set_fs_id( + <_c_.svn_fs_id_t *> + ((self._c_change)[0].node_rev_id), + self.result_pool), + (self._c_change)[0].change_kind, + (self._c_change)[0].text_mod, + (self._c_change)[0].prop_mod, + (self._c_change)[0].node_kind, + (self._c_change)[0].copyfrom_known, + (self._c_change)[0].copyfrom_rev, + copyfrom_path, + (self._c_change)[0].mergeinfo_mod) + ELIF SVN_API_VER >= (1, 6): + return FsPathChange( + svn_fs_id_t().set_fs_id( + <_c_.svn_fs_id_t *> + ((self._c_change)[0].node_rev_id), + self.result_pool), + (self._c_change)[0].change_kind, + (self._c_change)[0].text_mod, + (self._c_change)[0].prop_mod, + (self._c_change)[0].node_kind, + (self._c_change)[0].copyfrom_known, + (self._c_change)[0].copyfrom_rev, + copyfrom_path) + ELSE: + return FsPathChange( + svn_fs_id_t().set_fs_id( + <_c_.svn_fs_id_t *> + ((self._c_change)[0].node_rev_id), + self.result_pool), + (self._c_change)[0].change_kind, + (self._c_change)[0].text_mod, + (self._c_change)[0].prop_mod) + IF SVN_API_VER >= (1, 6): + cdef void set_c_change( + self, _c_.svn_fs_path_change2_t * _c_change, + object result_pool): + assert (<_svn.Apr_Pool?>result_pool)._c_pool is not NULL + self._c_change = _c_change + self.result_pool = result_pool + ELSE: + cdef void set_c_change( + self, _c_.svn_fs_path_change_t * _c_change, + object result_pool): + assert (<_svn.Apr_Pool?>result_pool)._c_pool is not NULL + self._c_change = _c_change + self._c_change = _c_change + cdef void ** ptr_ref(self): + return &(self._c_change) + +# warn: this function doesn't provide full functionally +# (not return apr_hash object but dict, and its contents is neither +# svn_fs_path_change2_t nor svn_fs_path_change_t object but python +# object, which cannot be used for arguments for other svn wrapper APIs +# directly) +def svn_fs_paths_changed( + svn_fs_root_t root, result_pool=None, scratch_pool=None): + cdef _c_.apr_status_t ast + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + IF SVN_API_VER >= (1, 10): + cdef _svn.Apr_Pool tmp_pool + cdef _c_.svn_fs_path_change_iterator_t * _c_iterator + cdef _c_.svn_fs_path_change3_t * _c_change + cdef object copyfrom_path + cdef object changed_path + ELSE: + cdef _svn.Apr_Pool r_pool + cdef _svn.HashTrans pt_trans + + IF SVN_API_VER >= (1, 10): + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + tmp_pool = _svn.Apr_Pool(scratch_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + tmp_pool = _svn.Apr_Pool(_svn._scratch_pool) + + change = {} + # If Suversion C API >= 1.10, our result doesn't content + # any object allocate from pool, so we can use tmp_pool safely + serr = _c_.svn_fs_paths_changed3( + &_c_iterator, root._c_ptr, + tmp_pool._c_pool, tmp_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + serr = _c_.svn_fs_path_change_get(&_c_change, _c_iterator) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + while _c_change is not NULL: + copyfrom_path = ((_c_change[0].copyfrom_path) + if _c_change[0].copyfrom_path is not NULL + else None) + changed_path = (_c_change[0].path.data)[:_c_change[0].data.len] + IF PY_VERSION >= (3, 0, 0): + copyfrom_path = _svn._norm(copyfrom_path) + changed_path = _svn._norm(changed_path) + change[changed_path] = \ + FsPathChange(_c_change[0].change_kind, + _c_change[0].node_kind, + _c_change[0].text_mod, + _c_change[0].prop_mod, + _c_change[0].mergeinfo_mod, + _c_change[0].copyfrom_known, + copyfrom_path) + serr = _c_.svn_fs_path_change_get(&_c_change, _c_iterator) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + ELSE: + # As svn_fs_id_t object shall be allocated from result_pool + # as a part of content of svn_fs_change2_t or svn_fs_change_t, + # we must let FsPathChangeTrans know where it is allocated from. + if result_pool is not None: + assert (<_svn.Apr_Pool?>result_pool)._c_pool is not NULL + r_pool = result_pool + else: + r_pool = _svn._root_pool + pt_trans = HashTrans(CstringTransStr(), + FsPathChangeTrans(r_pool, scratch_pool), + scratch_pool) + IF SVN_API_VER >= (1, 6): + serr = _c_.svn_fs_paths_changed2( + <_c_.apr_hash_t **>(pt_trans.ptr_ref()), + root._c_ptr, r_pool._c_pool) + ELSE: + serr = _c_.svn_fs_paths_changed( + <_c_.apr_hash_t **>(pt_trans.ptr_ref()), + root._c_ptr, r_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + change = pt_trans.to_object() + + return change + + +cdef class NodeKindTrans(_svn.TransPtr): + cdef object to_object(self): + return self._c_kind + cdef void set_c_kind(self, _c_.svn_node_kind_t _c_kind): + self._c_kind = _c_kind + cdef void ** ptr_ref(self): + return &(self._c_kind) + + +def svn_fs_check_path( + svn_fs_root_t root, object path, scratch_pool=None): + return _apply_svn_api_root_path_arg1( + _c_.svn_fs_check_path, + NodeKindTrans(), + root, path, scratch_pool) + + +cdef class svn_fs_history_t(object): + # cdef _c_.svn_fs_history_t * _c_ptr + def __cinit__(self): + self._c_ptr = NULL + self.pool = None + cdef set_history(self, _c_.svn_fs_history_t * history, object pool): + assert pool is None or isinstance(pool, _svn.Apr_Pool) + self.pool = pool + self._c_ptr = history + return self + + +# warn: though result_pool is optional, ommiting to specify it causes +# allocation from global pool, and not releases its allocation until +# the program terminates. (scratch_pool is used only if API version >= 1.9) +def svn_fs_node_history( + svn_fs_root_t root, object path, result_pool=None, scratch_pool=None): + cdef const char * _c_path + cdef _c_.svn_fs_history_t * _c_history + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _svn.Apr_Pool r_pool + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if result_pool is not None: + assert (<_svn.Apr_Pool>result_pool)._c_pool is not NULL + r_pool = result_pool + else: + r_pool = _svn._root_pool + IF SVN_API_VER >= (1, 9): + if scratch_pool is not None: + assert (<_svn.Apr_Pool>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + IF SVN_API_VER >= (1, 9): + serr = _c_.svn_fs_node_history2( + &_c_history, root._c_ptr, _c_path, + r_pool._c_pool, _c_tmp_pool) + ELSE: + serr = _c_.svn_fs_node_history( + &_c_history, root._c_ptr, _c_path, + r_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + IF SVN_API_VER >= (1, 9): + _c_.apr_pool_destroy(_c_tmp_pool) + ELSE: + pass + return svn_fs_history_t().set_history(_c_history, r_pool) + +# warn: though result_pool is optional, ommiting to specify it causes +# allocation from global pool, and not releases its allocation until +# the program terminates. (scratch_pool is used only if API version >= 1.9) +def svn_fs_history_prev( + svn_fs_history_t history, object cross_copies, + result_pool=None, scratch_pool=None): + cdef _c_.svn_fs_history_t * _c_prev + cdef _c_.svn_boolean_t _c_cross_copies + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _svn.Apr_Pool r_pool + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + if result_pool is not None: + assert (<_svn.Apr_Pool>result_pool)._c_pool is not NULL + r_pool = result_pool + else: + r_pool = _svn._root_pool + _c_cross_copies = True if cross_copies else False + IF SVN_API_VER >= (1, 9): + if scratch_pool is not None: + assert (<_svn.Apr_Pool>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + IF SVN_API_VER >= (1, 9): + serr = _c_.svn_fs_history_prev2( + &_c_prev, history._c_ptr, _c_cross_copies, + r_pool._c_pool, _c_tmp_pool) + ELSE: + serr = _c_.svn_fs_history_prev( + &_c_prev, history._c_ptr, _c_cross_copies, + r_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + IF SVN_API_VER >= (1, 9): + _c_.apr_pool_destroy(_c_tmp_pool) + ELSE: + pass + return svn_fs_history_t().set_history(_c_prev, r_pool) + + +def svn_fs_history_location(svn_fs_history_t history, scratch_pool=None): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_revnum_t revision + cdef const char * _c_path + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + serr = _c_.svn_fs_history_location( + &_c_path, &revision, history._c_ptr, _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + path = _c_path + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return path, revision + + +def svn_fs_is_dir(svn_fs_root_t root, object path, scratch_pool=None): + return _apply_svn_api_root_path_arg1( + _c_.svn_fs_is_dir, + _svn.SvnBooleanTrans(), + root, path, scratch_pool) + + +def svn_fs_is_file(svn_fs_root_t root, object path, scratch_pool=None): + return _apply_svn_api_root_path_arg1( + _c_.svn_fs_is_file, + _svn.SvnBooleanTrans(), + root, path, scratch_pool) + + +# warn: though result_pool is optional, ommiting to specify it causes +# allocation from global pool, and not releases its allocation until +# the program terminates. +def svn_fs_node_id(svn_fs_root_t root, object path, result_pool=None): + cdef _svn.Apr_Pool r_pool + cdef const char * _c_path + cdef _c_.svn_fs_id_t * _c_id + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if result_pool is not None: + assert (<_svn.Apr_Pool?>result_pool)._c_pool is not NULL + r_pool = result_pool + else: + r_pool = _svn._root_pool + serr = _c_.svn_fs_node_id( + &_c_id, + root._c_ptr, _c_path, r_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + return svn_fs_id_t().set_fs_id(_c_id, r_pool) + +def svn_fs_node_created_rev( + svn_fs_root_t root, object path, scratch_pool=None): + cdef _c_.apr_status_t ast + cdef const char * _c_path + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_revnum_t _c_revision + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + serr = _c_.svn_fs_node_created_rev( + &_c_revision, root._c_ptr, _c_path, _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return _c_revision + + +# warn: this function doesn't provide full functionally +# (not return a apr_hash object but a dict of which key is Python str object, +# and its contents is not svn_string_t but python str objects. +# So it cannot be used for arguments for other svn wrapper APIs directly) +def svn_fs_node_proplist( + svn_fs_root_t root, object path, scratch_pool=None): + return _apply_svn_api_root_path_arg1( + _c_.svn_fs_node_proplist, + _svn.HashTrans(_svn.CStringTransStr(), + _svn.SvnStringTransStr(), + scratch_pool), + root, path, scratch_pool) + + +def svn_fs_copied_from( + svn_fs_root_t root, object path, scratch_pool=None): + cdef const char * _c_path + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_revnum_t _c_rev + cdef const char * _c_from_path + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + _svn._scratch_pool.clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + serr = _c_.svn_fs_copied_from( + &_c_rev, &_c_from_path, root._c_ptr, path, _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + from_path = ((_c_from_path) if _c_from_path is not NULL + else None) + IF PY_VERSION >= (3, 0, 0): + from_path = _svn._norm(from_path) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return _c_rev, from_path + + +class VCDirEntry(object): + def __init__(self, name, kind): + IF PY_VERSION >= (3, 0, 0): + self.name = _svn._norm(name) + ELSE: + self.name = name + self.kind = kind + +# transform content of svn_fs_dirent_t into Python object. +# We don't need node_rev_id member here. +# As I don't want to import vclib here, translation map of node kind is +# needed to be passed by caller. +cdef class _VCDirEntryTrans(_svn.TransPtr): + cdef _c_.svn_fs_dirent_t *_c_dirent + cdef dict kind_map + def __init__(self, kind_map, **m): + self.kind_map = kind_map + cdef object to_object(self): + cdef object name + name = (self._c_dirent)[0].name + kind = self.kind_map.get((self._c_dirent)[0].kind, None) + return VCDirEntry(name, kind) + cdef void set_c_dirent( + self, _c_.svn_fs_dirent_t *_c_dirent): + self._c_dirent = _c_dirent + cdef void ** ptr_ref(self): + return &(self._c_dirent) + +def _listdir_helper( + svn_fs_root_t root, object path, object kind_map, + scratch_pool=None): + assert isinstance(kind_map, dict) + return _apply_svn_api_root_path_arg1( + _c_.svn_fs_dir_entries, + _svn.HashTrans(_svn.CStringTransBytes(), + _VCDirEntryTrans(kind_map), + scratch_pool), + root, path, scratch_pool) + + +cdef class FileSizeTrans(_svn.TransPtr): + cdef object to_object(self): + return (self._c_fsize) + cdef void set_filesize(self, _c_.svn_filesize_t _c_fsize): + self._c_fsize = _c_fsize + cdef void ** ptr_ref(self): + return &(self._c_fsize) + +def svn_fs_file_length( + svn_fs_root_t root, object path, scratch_pool=None): + return _apply_svn_api_root_path_arg1( + _c_.svn_fs_file_length, + FileSizeTrans(), + root, path, scratch_pool) + +# warn: though pool is optional, ommiting to specify it causes +# allocation from global pool, and not releases its allocation until +# the program terminates. +def svn_fs_file_contents(svn_fs_root_t root, object path, pool=None): + cdef const char * _c_path + cdef _c_.apr_status_t ast + cdef _svn.Apr_Pool r_pool + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef _c_.svn_stream_t * _c_contents + cdef _svn.svn_stream_t contents + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if pool is not None: + assert (<_svn.Apr_Pool?>pool)._c_pool is not NULL + r_pool = pool + else: + r_pool = _svn._root_pool + serr = _c_.svn_fs_file_contents( + &_c_contents, root._c_ptr, _c_path, r_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + contents = _svn.svn_stream_t() + contents.set_stream(_c_contents, r_pool) + return contents + + +def svn_fs_youngest_rev(svn_fs_t fs, scratch_pool=None): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_revnum_t _c_rev + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + serr = _c_.svn_fs_youngest_rev( + &_c_rev, fs._c_ptr, _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return _c_rev + + +# warn: this function doesn't provide full functionally +# (not return a apr_hash object but a dict of which key is Python str object, +# and its contents is not svn_string_t but python str objects. +# So it cannot be used for arguments for other svn wrapper APIs directly) +def svn_fs_revision_proplist( + svn_fs_t fs, _c_.svn_revnum_t rev, scratch_pool=None): + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef _c_.apr_hash_t * _c_tp + cdef _svn.HashTrans prop_trans + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + prop_trans = _svn.HashTrans(_svn.CStringTransStr(), + _svn.SvnStringTransStr(), scratch_pool) + IF SVN_API_VER >= (1, 10): + serr = _c_.svn_fs_revision_proplist2( + <_c_.apr_hash_t **>(prop_trans.ptr_ref()), + fs._c_ptr, rev, _c_.TRUE, + _c_tmp_pool, _c_tmp_pool) + ELSE: + serr = _c_.svn_fs_revision_proplist( + <_c_.apr_hash_t **>(prop_trans.ptr_ref()), + fs._c_ptr, rev, _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return prop_trans.to_object() + + +# This class placeholder of contents of svn_lock_t, enough to the extent +# to use from svn_repos.py[x], but not provide full function. +cdef class SvnLock(object): + def __init__( + self, object path, object token, object owner, object comment, + _c_.svn_boolean_t is_dav_comment, + _c_.apr_time_t creation_date, _c_.apr_time_t expiration_date): + self.path = path + self.token = token + self.owner = owner + self.comment = comment + self.is_dav_comment = is_dav_comment + self.creation_date = creation_date + self.expiration_date = expiration_date + + +cdef object _svn_lock_to_object(const _c_.svn_lock_t * _c_lock): + cdef object path + cdef object token + cdef object owner + cdef object comment + if _c_lock is NULL: + return None + else: + if _c_lock[0].path is NULL: + path = None + else: + path = (_c_lock[0].path) + IF PY_VERSION >= (3, 0, 0): + path = _svn._norm(path) + if _c_lock[0].token is NULL: + token = None + else: + token = (_c_lock[0].token) + IF PY_VERSION >= (3, 0, 0): + token = _svn._norm(token) + if _c_lock[0].owner is NULL: + owner = None + else: + owner = (_c_lock[0].owner) + IF PY_VERSION >= (3, 0, 0): + owner = _svn._norm(owner) + if _c_lock[0].comment is NULL: + comment = None + else: + comment = (_c_lock[0].comment) + IF PY_VERSION >= (3, 0, 0): + comment = _svn._norm(comment) + is_dav_comment = (True if _c_lock[0].is_dav_comment != _c_.FALSE + else False) + return SvnLock(path, token, owner, comment, is_dav_comment, + _c_lock[0].creation_date, _c_lock[0].expiration_date) + + +# warn: this function doesn't provide full functionally +# (not return a svn_lock_t object but pure Python SvnLock object. +# So it cannot be used for arguments for other svn wrapper APIs directly) +def svn_fs_get_lock(svn_fs_t fs, object path, scratch_pool=None): + cdef const char * _c_path + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + cdef _c_.svn_lock_t * _c_lock + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + serr = _c_.svn_fs_get_lock( + &_c_lock, fs._c_ptr, _c_path, _c_tmp_pool) + lock = _svn_lock_to_object(_c_lock) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return lock + + +cdef class svn_repos_t(object): + # cdef _c_.svn_repos_t * _c_ptr + def __cinit__(self): + self._c_ptr = NULL + self.pool = None + cdef svn_repos_t set_repos( + svn_repos_t self, _c_.svn_repos_t * repos, object pool): + assert (<_svn.Apr_Pool?>pool)._c_pool is not NULL + self._c_ptr = repos + self.pool = pool + return self + +# this is only for svn_repos.py{x}, does not provide full function +# but try to newer API. +def svn_repos_open(object path, result_pool=None, scratch_pool=None): + cdef const char * _c_path + cdef _c_.svn_repos_t * _c_repos + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _svn.Apr_Pool r_pool + cdef _c_.svn_error_t * serr + cdef _svn.Svn_error pyerr + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + if result_pool is not None: + assert (<_svn.Apr_Pool?>result_pool)._c_pool is not NULL + r_pool = result_pool + else: + r_pool = _svn._root_pool + IF SVN_API_VER >= (1, 9): + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>scratch_pool)._c_pool) + else: + (<_svn.Apr_Pool?>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + IF SVN_API_VER >= (1, 9): + serr = _c_.svn_repos_open3( + &_c_repos, _c_path, NULL, r_pool._c_pool, _c_tmp_pool) + ELIF SVN_API_VER >= (1, 7): + serr = _c_.svn_repos_open2( + &_c_repos, _c_path, NULL, r_pool._c_pool) + ELSE: + serr = _c_.svn_repos_open( + &_c_repos, _c_path, r_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + IF SVN_API_VER >= (1, 9): + _c_.apr_pool_destroy(_c_tmp_pool) + ELSE: + pass + return svn_repos_t().set_repos(_c_repos, r_pool) + + +def svn_repos_fs(svn_repos_t repos): + return svn_fs_t().set_fs(_c_.svn_repos_fs(repos._c_ptr), repos.pool) + + +cdef _c_.svn_error_t * _cb_svn_repos_authz_func_wrapper( + _c_.svn_boolean_t * _c_allowed, _c_.svn_fs_root_t * _c_root, + const char * _c_path, void * baton, _c_.apr_pool_t * _c_pool) with gil: + cdef _svn.CbContainer btn + cdef svn_fs_root_t root + cdef object path + cdef _svn.Apr_Pool pool + cdef object allowed + cdef object pyerr + cdef _svn.Svn_error svnerr + cdef _c_.svn_error_t * _c_err + + btn = <_svn.CbContainer>baton + root = svn_fs_root_t().set_fs_root(_c_root, None) + path = _c_path + IF PY_VERSION >= (3, 0, 0): + path = _svn._norm(path) + pool = _svn.Apr_Pool.__new__(_svn.Apr_Pool, pool=None) + pool.set_pool(_c_pool) + _c_err = NULL + try: + allowed = btn.fnobj(root, path, btn.btn, pool) + _c_allowed[0] = _c_.TRUE if allowed else _c_.FALSE + except _svn.SVNerr as pyerr: + svnerr = pyerr.svnerr + _c_err = _c_.svn_error_dup(svnerr.geterror()) + del pyerr + except AssertionError as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_ASSERTION_FAIL, NULL, str(err)) + except KeyboardInterrupt as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, str(err)) + except BaseException as err: + _c_err = _c_.svn_error_create( + _c_.SVN_ERR_BASE, NULL, str(err)) + return _c_err + + +def svn_repos_trace_node_locations( + svn_fs_t fs, object fs_path, _c_.svn_revnum_t peg_revision, + object location_revisions, object authz_read_func, + object authz_read_baton, object scratch_pool=None): + cdef const char * _c_fs_path + cdef _c_.apr_status_t ast + cdef _svn.Apr_Pool tmp_pool + cdef _c_.apr_array_header_t * _c_location_rivisions + cdef _svn.CbContainer btn + cdef _svn.SvnRevnumPtrTrans revtrans + cdef _svn.CStringTransBytes transbytes + cdef _svn.HashTrans loctrans + cdef _c_.svn_error_t * serr + cdef object locations + + # make sure fs_path is a bytes object + if isinstance(fs_path, PathLike): + fs_path = fs_path.__fspath__() + if not isinstance(fs_path, bytes) and isinstance(fs_path, str): + fs_path = fs_path.encode('utf-8') + _c_fs_path = fs_path + + assert callable(authz_read_func) + if scratch_pool is not None: + assert (<_svn.Apr_Pool?>scratch_pool)._c_pool is not NULL + tmp_pool = _svn.Apr_Pool(scratch_pool) + else: + tmp_pool = _svn.Apr_Pool(_svn._scratch_pool) + try: + _c_location_revisions = _svn.make_revnum_array(location_revisions, + tmp_pool._c_pool) + btn = _svn.CbContainer(authz_read_func, authz_read_baton, tmp_pool) + loctrans = _svn.HashTrans(_svn.SvnRevnumPtrTrans(), + _svn.CStringTransStr(), + tmp_pool) + serr = _c_.svn_repos_trace_node_locations( + fs._c_ptr, <_c_.apr_hash_t **>(loctrans.ptr_ref()), + _c_fs_path, peg_revision, _c_location_revisions, + _cb_svn_repos_authz_func_wrapper, btn, + tmp_pool._c_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + locations = loctrans.to_object() + finally: + del tmp_pool + return locations + + +# vclib custom revinfo helper +# copy from subversion/bindings/swig/python/svn/repos.py, class ChangedPath, +# with Cython and customize for vclib.svn.svn_repos +cdef class _ChangedPath(object): + # cdef _c_.svn_node_kind_t item_kind + # cdef _c_.svn_boolean_t prop_changes + # cdef _c_.svn_boolean_t text_changed + # cdef object base_path # (bytes for Python 2, str for Python 3) + # cdef _c_.svn_revnum_t base_rev + # cdef object path # (bytes for Python 2, str for Python 3) + # cdef _c_.svn_boolean_t added + ### we don't use 'None' action + # cdef _c_.svn_fs_path_change_kind_t action + def __init__(self, + _c_.svn_node_kind_t item_kind, + _c_.svn_boolean_t prop_changes, + _c_.svn_boolean_t text_changed, + object base_path, _c_.svn_revnum_t base_rev, + object path, _c_.svn_boolean_t added, + _c_.svn_fs_path_change_kind_t action): + self.item_kind = item_kind + self.prop_changes = prop_changes + self.text_changed = text_changed + self.base_path = base_path + self.base_rev = base_rev + self.path = path + self.added = added + self.action = action + +cdef const char * _c_make_base_path( + const char * parent_path, const char * path, + _c_.apr_pool_t * pool): + cdef const char * cpivot + cdef char * pivot + cdef size_t p_len + cdef int need_slash + cdef const char * bnh # base name head + cdef size_t b_len + cdef char * new_base + + p_len = 0 + if parent_path[0] == 0: + need_slash = 0 + else: + cpivot = parent_path + p_len += 1 + while cpivot[1] != 0: + p_len += 1 + cpivot += 1 + if cpivot[0] != 47: # 47 == ord('/') + need_slash = 1 + bnh = path + cpivot = path + while cpivot[0] != 0: + if cpivot[0] == 47: + bnh = cpivot + 1 + cpivot += 1 + b_len = cpivot - bnh + new_base = _c_.apr_palloc(pool, (p_len + need_slash + b_len + 1)) + if new_base == NULL: + return NULL + if p_len: + memcpy(new_base, parent_path, p_len) + pivot = new_base + p_len + if need_slash: + pivot[0] = 47 + pivot += 1 + if b_len: + memcpy(pivot, bnh, b_len) + pivot[b_len] = 0 + return new_base + + +cdef class _get_changed_paths_EditBaton(object): + def __init__(self, svn_fs_t fs_ptr, svn_fs_root_t root): + self.changes = {} + self.fs_ptr = fs_ptr + self.root = root + # in vclib, svn_fs_root_t root is always revision root + assert _c_.svn_fs_is_revision_root(root._c_ptr) + self.base_rev = ( + _c_.svn_fs_revision_root_revision(root._c_ptr) - 1) + # self._c_p_pool is not initialized here, because this is + # C pointer which cannot be passed through Pure Python function + def _getroot(self, rev): + return self.fs_ptr._getroot(rev) + + +# custom call back functions used by get_changed_paths(), derived from +# subversion/bindings/swig/python/svn/repos.py, class ChangedCollector, +# with Cython +cdef _c_.svn_error_t * _cb_changed_paths_open_root( + void * _c_edit_baton, _c_.svn_revnum_t base_revision, + _c_.apr_pool_t * result_pool, void ** _c_root_baton) with gil: + cdef _get_changed_paths_EditBaton eb + cdef _get_changed_paths_DirBaton * rb + cdef _c_.svn_error_t * _c_err + + eb = <_get_changed_paths_EditBaton>_c_edit_baton + rb = <_get_changed_paths_DirBaton *>_c_.apr_palloc( + eb._c_p_pool, sizeof(_get_changed_paths_DirBaton)) + if rb is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + rb[0].path = _c_.apr_palloc(eb._c_p_pool, 1) + if rb[0].path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + ((rb[0].path))[0] = 0 + rb[0].base_path = rb[0].path + rb[0].base_rev = eb.base_rev + rb[0].edit_baton = eb + _c_root_baton[0] = rb + return NULL + +cdef _c_.svn_error_t * _cb_changed_paths_delete_entry( + const char * _c_path, _c_.svn_revnum_t revision, + void * parent_baton, _c_.apr_pool_t * scratch_pool) with gil: + cdef _get_changed_paths_DirBaton * pb + cdef _get_changed_paths_EditBaton eb + cdef object path + cdef object base_path + cdef const char * _c_base_path + cdef _c_.svn_error_t * _c_err + cdef _c_.svn_node_kind_t item_type + + pb = <_get_changed_paths_DirBaton *>parent_baton + eb = <_get_changed_paths_EditBaton>(pb[0].edit_baton) + path = _c_path + IF PY_VERSION >= (3, 0, 0): + path = _svn._norm(path) + _c_base_path = _c_make_base_path(pb[0].base_path, _c_path, scratch_pool) + if _c_base_path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + base_path = _c_base_path + if svn_fs_is_dir(eb._getroot(pb.base_rev), base_path): + item_type = _c_.svn_node_dir + else: + item_type = _c_.svn_node_file + IF PY_VERSION >= (3, 0, 0): + base_path = _svn._norm(base_path) + eb.changes[path] = _ChangedPath(item_type, + _c_.FALSE, + _c_.FALSE, + base_path, + pb[0].base_rev, + path, + _c_.FALSE, + _c_.svn_fs_path_change_delete) + return NULL + +cdef _c_.svn_error_t * _cb_changed_paths_add_directory( + const char * _c_path, void * parent_baton, + const char * _c_copyfrom_path, _c_.svn_revnum_t copyfrom_revision, + _c_.apr_pool_t * result_pool, void ** child_baton) with gil: + cdef _get_changed_paths_DirBaton * pb + cdef _get_changed_paths_EditBaton eb + cdef object path + cdef object copyfrom_path + cdef _get_changed_paths_DirBaton * cb + cdef _c_.svn_error_t * _c_err + cdef _c_.svn_fs_path_change_kind_t action + + pb = <_get_changed_paths_DirBaton *>parent_baton + eb = <_get_changed_paths_EditBaton>(pb[0].edit_baton) + path = _c_path + path = _c_path + IF PY_VERSION >= (3, 0, 0): + path = _svn._norm(path) + if _c_copyfrom_path is NULL: + copyfrom_path = None + else: + copyfrom_path = _c_copyfrom_path + IF PY_VERSION >= (3, 0, 0): + copyfrom_path = _svn._norm(copyfrom_path) + if path in eb.changes: + action = _c_.svn_fs_path_change_replace + else: + action = _c_.svn_fs_path_change_add + eb.changes[path] = _ChangedPath(_c_.svn_node_dir, + _c_.FALSE, + _c_.FALSE, + copyfrom_path, + copyfrom_revision, + path, + _c_.TRUE, + action) + if copyfrom_path and (copyfrom_revision >= 0): + _c_base_path = _c_copyfrom_path + else: + _c_base_path = _c_path + cb = <_get_changed_paths_DirBaton *>_c_.apr_palloc( + eb._c_p_pool, sizeof(_get_changed_paths_DirBaton)) + if cb is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + cb[0].path = _c_.apr_pstrdup(eb._c_p_pool, _c_path) + if cb[0].path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + cb[0].base_path = _c_.apr_pstrdup(eb._c_p_pool, _c_base_path) + if cb[0].base_path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + cb[0].base_rev = <_c_.svn_revnum_t>copyfrom_revision + cb[0].edit_baton = eb + child_baton[0] = cb + return NULL + +cdef _c_.svn_error_t * _cb_changed_paths_open_directory( + const char * _c_path, void * parent_baton, + _c_.svn_revnum_t base_revision, + _c_.apr_pool_t * result_pool, void ** child_baton) with gil: + cdef _get_changed_paths_DirBaton * pb + cdef _get_changed_paths_EditBaton eb + cdef _get_changed_paths_DirBaton * cb + cdef _c_.svn_error_t * _c_err + + pb = <_get_changed_paths_DirBaton *>parent_baton + eb = <_get_changed_paths_EditBaton>(pb[0].edit_baton) + cb = <_get_changed_paths_DirBaton *>_c_.apr_palloc( + eb._c_p_pool, sizeof(_get_changed_paths_DirBaton)) + if cb is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + cb[0].path = _c_.apr_pstrdup(eb._c_p_pool, _c_path) + if cb[0].path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + cb[0].base_path = _c_make_base_path( + pb[0].base_path, _c_path, eb._c_p_pool) + if cb[0].base_path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + cb[0].base_rev = pb[0].base_rev + cb[0].edit_baton = eb + child_baton[0] = cb + return NULL + +cdef _c_.svn_error_t * _cb_changed_paths_change_dir_prop( + void * dir_baton, const char * name, + const _c_.svn_string_t * value, + _c_.apr_pool_t * scratch_pool) with gil: + cdef _get_changed_paths_DirBaton * db + cdef _get_changed_paths_EditBaton eb + cdef object db_path + cdef object db_base_path + db = <_get_changed_paths_DirBaton *>dir_baton + eb = <_get_changed_paths_EditBaton>(db[0].edit_baton) + db_path = (db[0].path) + IF PY_VERSION >= (3, 0, 0): + db_path = _svn._norm(db_path) + if db_path in eb.changes: + (<_ChangedPath>(eb.changes[db_path])).prop_changes = _c_.TRUE + else: + # can't be added or deleted, so this must be CHANGED + if db[0].base_path is NULL: + db_base_path = None + else: + db_base_path = (db[0].base_path) + IF PY_VERSION >= (3, 0, 0): + db_base_path = _svn._norm(db_base_path) + eb.changes[db_path] = _ChangedPath( + _c_.svn_node_dir, + _c_.TRUE, + _c_.FALSE, + db_base_path, + db[0].base_rev, + db_path, + _c_.FALSE, + _c_.svn_fs_path_change_modify) + return NULL + +cdef _c_.svn_error_t * _cb_changed_paths_add_file( + const char * _c_path, void * parent_baton, + const char * _c_copyfrom_path, + _c_.svn_revnum_t copyfrom_revision, + _c_.apr_pool_t * result_pool, void ** file_baton) with gil: + cdef _get_changed_paths_DirBaton * pb + cdef _get_changed_paths_EditBaton eb + cdef object path + cdef object copyfrom_path + cdef _get_changed_paths_DirBaton * fb + cdef _c_.svn_fs_path_change_kind_t action + cdef _c_.svn_error_t * _c_err + + pb = <_get_changed_paths_DirBaton *>parent_baton + eb = <_get_changed_paths_EditBaton >(pb[0].edit_baton) + path = _c_path + IF PY_VERSION >= (3, 0, 0): + path = _svn._norm(path) + if path in eb.changes: + action = _c_.svn_fs_path_change_replace + else: + action = _c_.svn_fs_path_change_add + if _c_copyfrom_path is NULL: + copyfrom_path = None + else: + copyfrom_path = _c_copyfrom_path + IF PY_VERSION >= (3, 0, 0): + copyfrom_path = _svn._norm(copyfrom_path) + eb.changes[path] = _ChangedPath(_c_.svn_node_file, + _c_.FALSE, + _c_.FALSE, + copyfrom_path, + copyfrom_revision, + path, + _c_.TRUE, + action) + if copyfrom_path is not None and (copyfrom_revision >= 0): + _c_base_path = _c_copyfrom_path + else: + _c_base_path = _c_path + fb = <_get_changed_paths_DirBaton *>_c_.apr_palloc( + result_pool, sizeof(_get_changed_paths_DirBaton)) + if fb is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + fb[0].path = _c_.apr_pstrdup(eb._c_p_pool, _c_path) + if fb[0].path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + fb[0].base_path = _c_.apr_pstrdup(eb._c_p_pool, _c_base_path) + if fb[0].base_path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + fb[0].base_rev = copyfrom_revision + fb[0].edit_baton = eb + file_baton[0] = fb + return NULL + +cdef _c_.svn_error_t * _cb_changed_paths_open_file( + const char * _c_path, void * parent_baton, + _c_.svn_revnum_t base_revision, + _c_.apr_pool_t * result_pool, void ** file_baton) with gil: + cdef _get_changed_paths_DirBaton * pb + cdef _get_changed_paths_EditBaton eb + cdef _get_changed_paths_DirBaton * fb + cdef _c_.svn_error_t * _c_err + + pb = <_get_changed_paths_DirBaton *>parent_baton + eb = <_get_changed_paths_EditBaton >(pb[0].edit_baton) + fb = <_get_changed_paths_DirBaton *>_c_.apr_palloc( + result_pool, sizeof(_get_changed_paths_DirBaton)) + if fb is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + fb[0].path = _c_.apr_pstrdup(eb._c_p_pool, _c_path) + if fb[0].path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + fb[0].base_path = _c_make_base_path( + pb[0].base_path, _c_path, eb._c_p_pool) + if fb[0].base_path is NULL: + _c_err = _c_.svn_error_create(_c_.APR_ENOMEM, NULL, NULL) + return _c_err + fb[0].base_rev = pb[0].base_rev + fb[0].edit_baton = eb + file_baton[0] = fb + return NULL + +cdef _c_.svn_error_t * _null_svn_texdelta_window_handler( + _c_.svn_txdelta_window_t * window, void * baton) nogil: + return NULL + +cdef _c_.svn_error_t * _cb_changed_paths_apply_textdelta( + void * file_baton, const char * base_checksum, + _c_.apr_pool_t * result_pool, + _c_.svn_txdelta_window_handler_t * handler, + void ** handler_baton) with gil: + cdef _get_changed_paths_DirBaton * fb + cdef _get_changed_paths_EditBaton eb + cdef object fb_path + cdef object fb_base_path + fb = <_get_changed_paths_DirBaton*>file_baton + eb = <_get_changed_paths_EditBaton>(fb[0].edit_baton) + fb_path = (fb[0].path) + fb_base_path = (fb[0].base_path) + IF PY_VERSION >= (3, 0, 0): + fb_path = _svn._norm(fb_path) + fb_base_path = _svn._norm(fb_base_path) + if fb_path in eb.changes: + (<_ChangedPath>(eb.changes[fb_path])).text_changed = _c_.TRUE + else: + eb.changes[fb_path] = _ChangedPath( + _c_.svn_node_file, + _c_.FALSE, + _c_.TRUE, + fb_base_path, + fb[0].base_rev, + fb_path, + _c_.FALSE, + _c_.svn_fs_path_change_modify) + # we know no handlers to be set + handler[0] = _null_svn_texdelta_window_handler + handler_baton[0] = NULL + return NULL + +cdef _c_.svn_error_t * _cb_changed_paths_change_file_prop( + void * file_baton, const char * name, + const _c_.svn_string_t * value, + _c_.apr_pool_t * scratch_pool) with gil: + cdef _get_changed_paths_DirBaton * fb + cdef _get_changed_paths_EditBaton eb + cdef object fb_path + cdef object fb_base_path + fb = <_get_changed_paths_DirBaton *>file_baton + eb = <_get_changed_paths_EditBaton >(fb[0].edit_baton) + fb_path = (fb[0].path) + fb_base_path = (fb[0].base_path) + IF PY_VERSION >= (3, 0, 0): + fb_path = _svn._norm(fb_path) + fb_base_path = _svn._norm(fb_base_path) + if fb_path in eb.changes: + (<_ChangedPath>(eb.changes[fb_path])).prop_changes = _c_.TRUE + else: + # can't be added or deleted, so this must be CHANGED + eb.changes[fb_path] = _ChangedPath( + _c_.svn_node_file, + _c_.TRUE, + _c_.FALSE, + fb_base_path, + fb[0].base_rev, + fb_path, + _c_.FALSE, + _c_.svn_fs_path_change_modify) + return NULL + +def _get_changed_paths_helper( + svn_fs_t fs, svn_fs_root_t fsroot, object pool=None): + cdef _c_.apr_status_t ast + cdef _c_.svn_error_t * serr + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.apr_pool_t * _c_p_pool + cdef _c_.svn_delta_editor_t * editor + cdef _get_changed_paths_EditBaton eb + cdef _svn.Svn_error pyerr + IF SVN_API_VER >= (1, 4): + cdef bytes base_dir + + if pool is not None: + assert ((<_svn.Apr_Pool?>pool)._c_pool is not NULL) + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>pool)._c_pool) + if ast: + raise _svn.PoolError() + ast = _c_.apr_pool_create( + &_c_p_pool, (<_svn.Apr_Pool>pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + ast = _c_.apr_pool_create( + &_c_p_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + _c_.apr_pool_destroy(_c_tmp_pool) + raise _svn.PoolError() + try: + editor = _c_.svn_delta_default_editor(_c_tmp_pool) + editor[0].open_root = _cb_changed_paths_open_root + editor[0].delete_entry = _cb_changed_paths_delete_entry + editor[0].add_directory = _cb_changed_paths_add_directory + editor[0].open_directory = _cb_changed_paths_open_directory + editor[0].change_dir_prop = _cb_changed_paths_change_dir_prop + editor[0].add_file = _cb_changed_paths_add_file + editor[0].open_file = _cb_changed_paths_open_file + editor[0].apply_textdelta = _cb_changed_paths_apply_textdelta + editor[0].change_file_prop = _cb_changed_paths_change_file_prop + eb = _get_changed_paths_EditBaton(fs, fsroot) + eb._c_p_pool = _c_p_pool + IF SVN_API_VER >= (1, 4): + base_dir = b'' + serr = _c_.svn_repos_replay2( + fsroot._c_ptr, base_dir, + _c_.SVN_INVALID_REVNUM, _c_.FALSE, + editor, eb, NULL, NULL, _c_tmp_pool) + ELSE: + serr = _c_.svn_repos_replay( + fsroot._c_ptr, editor, eb, _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + _c_.apr_pool_destroy(_c_p_pool) + _c_.apr_pool_destroy(_c_tmp_pool) + return eb.changes + + +# Cython version of vclib.svn.svn_repos.NodeHistory class +# (used as history baton on svn_repos_history2 call) +cdef class NodeHistory(object): + """A history baton object that builds list of 2-tuple (revision, path) + locations along a node's change history, orderd from youngest to + oldest.""" + cdef public list histories + cdef _c_.svn_revnum_t _item_cnt # cache of len(self.histories) + cdef svn_fs_t fs_ptr + cdef _c_.svn_boolean_t show_all_logs + cdef _c_.svn_revnum_t oldest_rev + cdef _c_.svn_revnum_t limit + def __init__(self, svn_fs_t fs_ptr, + _c_.svn_boolean_t show_all_logs, + _c_.svn_revnum_t limit): + self.histories = [] + self.fs_ptr = fs_ptr + self.show_all_logs = show_all_logs + self.oldest_rev = _c_.SVN_INVALID_REVNUM + self.limit = limit + +# call back function for _get_history_helper() +cdef _c_.svn_error_t * _cb_collect_node_history( + void * baton, const char * _c_path, _c_.svn_revnum_t revision, + _c_.apr_pool_t * pool) with gil: + cdef NodeHistory btn + cdef object changed_paths + cdef object path + cdef svn_fs_root_t rev_root + cdef bytes test_path + cdef _c_.svn_boolean_t found + cdef object off + cdef _c_.svn_revnum_t copyfrom_rev + cdef object copyfrom_path + cdef _c_.svn_error_t * serr + cdef _svn.Apr_Pool wrap_pool + btn = baton + wrap_pool = _svn.Apr_Pool.__new__(_svn.Apr_Pool,None) + wrap_pool.set_pool(pool) + if btn.oldest_rev == _c_.SVN_INVALID_REVNUM: + btn.oldest_rev = revision + else: + assert revision < btn.oldest_rev + path = _c_path + IF PY_VERSION >= (3, 0, 0): + path = _svn._norm(path) + if btn.show_all_logs == _c_.FALSE: + rev_root = btn.fs_ptr._getroot(revision) + changed_paths = svn_fs_paths_changed(rev_root, wrap_pool) + if path not in changed_paths: + # Look for a copied parent + test_path = path + found = _c_.FALSE + off = test_path.rfind('/') + while off >= 0: + test_path = test_path[0:off] + if test_path in changed_paths: + copyfrom_rev, copyfrom_path = \ + svn_fs_copied_from(rev_root, test_path, wrap_pool) + if copyfrom_rev >= 0 and copyfrom_path: + found = _c_.TRUE + break + off = test_path.rfind('/') + if found == _c_.FALSE: + return NULL + btn.histories.append([revision, + '/'.join([pp for pp in path.split('/') if pp])]) + btn._item_cnt += 1 + if btn.limit and btn._item_cnt >= btn.limit: + IF SVN_API_VER >= (1, 5): + serr = _c_.svn_error_create( + _c_.SVN_ERR_CEASE_INVOCATION, NULL, NULL) + ELSE: + serr = _c_.svn_error_create( + _c_.SVN_ERR_CANCELLED, NULL, NULL) + return serr + return NULL + +def _get_history_helper( + svn_fs_t fs_ptr, object path, + _c_.svn_revnum_t rev, _c_.svn_boolean_t cross_copies, + _c_.svn_boolean_t show_all_logs, + _c_.svn_revnum_t limit, pool=None): + cdef const char * _c_path + cdef _c_.apr_status_t ast + cdef _c_.apr_pool_t * _c_tmp_pool + cdef _c_.svn_error_t * serr + cdef NodeHistory nhbtn + + # make sure path is a bytes object + if isinstance(path, PathLike): + path = path.__fspath__() + if not isinstance(path, bytes) and isinstance(path, str): + path = path.encode('utf-8') + _c_path = path + + nhbtn = NodeHistory(fs_ptr, show_all_logs, limit) + if pool is not None: + assert (<_svn.Apr_Pool?>pool)._c_pool is not NULL + ast = _c_.apr_pool_create( + &_c_tmp_pool, (<_svn.Apr_Pool>pool)._c_pool) + else: + (<_svn.Apr_Pool>_svn._scratch_pool).clear() + ast = _c_.apr_pool_create( + &_c_tmp_pool, + (<_svn.Apr_Pool>_svn._scratch_pool)._c_pool) + if ast: + raise _svn.PoolError() + try: + serr = _c_.svn_repos_history2( + fs_ptr._c_ptr, path, _cb_collect_node_history, + nhbtn, NULL, NULL, 1, rev, cross_copies, + _c_tmp_pool) + if serr is not NULL: + pyerr = _svn.Svn_error().seterror(serr) + raise _svn.SVNerr(pyerr) + finally: + _c_.apr_pool_destroy(_c_tmp_pool) + return nhbtn.histories diff --git a/src/lib/vclib/altsvn/_svn_repos_capi.pxd b/src/lib/vclib/altsvn/_svn_repos_capi.pxd new file mode 100644 index 00000000..bf0b24e1 --- /dev/null +++ b/src/lib/vclib/altsvn/_svn_repos_capi.pxd @@ -0,0 +1,16 @@ +include "_svn_api_ver.pxi" +from apr_1.apr cimport * +from apr_1.apr_errno cimport * +from apr_1.apr_pools cimport * +from apr_1.apr_hash cimport * +from apr_1.apr_tables cimport * +from apr_1.apr_strings cimport * +from subversion_1.svn_types cimport * +from subversion_1.svn_error cimport * +from subversion_1.svn_error_codes cimport * +from subversion_1.svn_fs cimport * +from subversion_1.svn_io cimport * +from subversion_1.svn_delta cimport * +from subversion_1.svn_repos cimport * +from subversion_1.svn_string cimport * +from subversion_1.svn_props cimport * diff --git a/src/lib/vclib/altsvn/make_svn_api_version_pxi.c b/src/lib/vclib/altsvn/make_svn_api_version_pxi.c new file mode 100644 index 00000000..abea05d6 --- /dev/null +++ b/src/lib/vclib/altsvn/make_svn_api_version_pxi.c @@ -0,0 +1,24 @@ +# include +# if defined(NEED_SUBVERSION_1_DIR) +# include +# else +# include +# endif + +int +main(void) { + FILE * fp; + if (NULL == (fp = fopen("_svn_api_ver.pxi", "w"))) { + fprintf(stderr, "fail to write open : svn_api_ver.pxi\n"); + exit(1); + } + fprintf(fp, "DEF SVN_API_VER = (%ld, %ld)\n", + (long)SVN_VER_MAJOR, (long)SVN_VER_MINOR); +#if defined(WIN32) || defined(__CYGWIN__) || defined(__OS2__) + fprintf(fp, "DEF SVN_USE_DOS_PATHS = 1\n"); +#else + fprintf(fp, "DEF SVN_USE_DOS_PATHS = 0\n"); +#endif + fclose(fp); + exit(0); +} diff --git a/src/lib/vclib/altsvn/svn_ra.py b/src/lib/vclib/altsvn/svn_ra.py new file mode 100644 index 00000000..3f0f4b32 --- /dev/null +++ b/src/lib/vclib/altsvn/svn_ra.py @@ -0,0 +1,724 @@ +# -*-python-*- +# +# Copyright (C) 1999-2018 The ViewCVS Group. All Rights Reserved. +# +# By using this file, you agree to the terms and conditions set forth in +# the LICENSE.html file which can be found at the top level of the ViewVC +# distribution or at http://viewvc.org/license-1.html. +# +# For more information, visit http://viewvc.org/ +# +# ----------------------------------------------------------------------- + +"Version Control lib driver for remotely accessible Subversion repositories." + +import vclib +import sys +import os +import io +import re +import tempfile + +if sys.version_info[0] >= 3: + PY3 = True + import functools + from urllib.parse import quote as _quote +else: + PY3 = False + from urllib import quote as _quote + +from . import _svn, _svn_ra, _path_parts, _cleanup_path,\ + _compare_paths, _split_revprops, Revision, SVNChangedPath +#from svn import client + + +### Require Subversion 1.3.1 or better. (for svn_ra_get_locations support) +# foolproof. because _svn cannot build with API version below 1.4 +if (_svn.SVN_VER_MAJOR, _svn.SVN_VER_MINOR, _svn.SVN_VER_PATCH) < (1, 3, 1): + raise vclib.Error("Version requirement not met (needs 1.3.1 or better)") + + +class LogCollector: + + def __init__(self, path, show_all_logs, lockinfo, access_check_func): + # This class uses leading slashes for paths internally + if not path: + self.path = '/' + else: + self.path = path[0] == '/' and path or '/' + path + self.logs = [] + self.show_all_logs = show_all_logs + self.lockinfo = lockinfo + self.access_check_func = access_check_func + self.done = False + + @staticmethod + def add_log(self, log_entry, pool): + if self.done: + return + paths = log_entry.changed_paths + revision = log_entry.revision + msg, author, date, revprops = _split_revprops(log_entry.revprops) + + # Changed paths have leading slashes + if PY3: + changed_paths = list(paths.keys()) + changed_paths.sort(key=functools.cmp_to_key( + lambda a, b: _compare_paths(a, b))) + else: + changed_paths = paths.keys() + changed_paths.sort(lambda a, b: _compare_paths(a, b)) + this_path = None + if self.path in changed_paths: + this_path = self.path + change = paths[self.path] + if change.copyfrom_path: + this_path = change.copyfrom_path + for changed_path in changed_paths: + if changed_path != self.path: + # If a parent of our path was copied, our "next previous" + # (huh?) path will exist elsewhere (under the copy source). + if (self.path.rfind(changed_path) == 0) and \ + self.path[len(changed_path)] == '/': + change = paths[changed_path] + if change.copyfrom_path: + this_path = change.copyfrom_path + self.path[len(changed_path):] + if self.show_all_logs or this_path: + if self.access_check_func is None \ + or self.access_check_func(self.path[1:], revision): + entry = Revision(revision, date, author, msg, None, self.lockinfo, + self.path[1:], None, None) + self.logs.append(entry) + else: + self.done = True + if this_path: + self.path = this_path + +def cat_to_tempfile(svnrepos, path, rev, scratch_pool): + """Check out file revision to temporary file""" + fd, temp = tempfile.mkstemp() + fp = io.open(fd, 'wb') + stream = _svn.py_io_stream(fp, scratch_pool) + url = svnrepos._geturl(path) + _svn_ra.svn_client_cat(stream, url, rev, rev, False, False, + svnrepos.ctx, scratch_pool) + _svn.svn_stream_close(stream) + return temp + +class SelfCleanFP: + def __init__(self, path): + self._fp = open(path, 'rb') + self._path = path + self._eof = 0 + + def read(self, len=None): + if len: + chunk = self._fp.read(len) + else: + chunk = self._fp.read() + if chunk == b'': + self._eof = 1 + return chunk + + def readline(self): + chunk = self._fp.readline() + if chunk == b'': + self._eof = 1 + return chunk + + def readlines(self): + lines = self._fp.readlines() + self._eof = 1 + return lines + + def close(self): + self._fp.close() + if self._path: + try: + os.remove(self._path) + self._path = None + except OSError: + pass + + def __del__(self): + self.close() + + def eof(self): + return self._eof + + +class RemoteSubversionRepository(vclib.Repository): + def __init__(self, name, rootpath, authorizer, utilities, config_dir): + self.name = name + self.rootpath = rootpath + self.auth = authorizer + self.diff_cmd = utilities.diff or 'diff' + self.config_dir = config_dir or None + self.result_pool = _svn.Apr_Pool() + self.scratch_pool = _svn.Apr_Pool() + + # See if this repository is even viewable, authz-wise. + if not vclib.check_root_access(self): + raise vclib.ReposNotFound(name) + + def open(self): + # Setup the client context baton, complete with non-prompting authstuffs. + self.ctx = _svn.setup_client_ctx(self.config_dir, self.result_pool) + self.ra_session = _svn_ra.open_session_with_ctx(self.rootpath, self.ctx) + + self.youngest = _svn_ra.svn_ra_get_latest_revnum( + self.ra_session, self.scratch_pool) + self._dirent_cache = { } + self._revinfo_cache = { } + + # See if a universal read access determination can be made. + if self.auth and self.auth.check_universal_access(self.name) == 1: + self.auth = None + self.scratch_pool.clear() + + def rootname(self): + return self.name + + def rootpath(self): + return self.rootpath + + def roottype(self): + return vclib.SVN + + def authorizer(self): + return self.auth + + def itemtype(self, path_parts, rev): + pathtype = None + if not len(path_parts): + pathtype = vclib.DIR + else: + path = self._getpath(path_parts) + rev = self._getrev(rev) + try: + kind = _svn_ra.svn_ra_check_path( + self.ra_session, path, rev, self.scratch_pool) + if kind == _svn.svn_node_file: + pathtype = vclib.FILE + elif kind == _svn.svn_node_dir: + pathtype = vclib.DIR + except: + pass + if pathtype is None: + raise vclib.ItemNotFound(path_parts) + if not vclib.check_path_access(self, path_parts, pathtype, rev): + raise vclib.ItemNotFound(path_parts) + return pathtype + + def openfile(self, path_parts, rev, options): + path = self._getpath(path_parts) + if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check + raise vclib.Error("Path '%s' is not a file." % _svn._norm(path)) + rev = self._getrev(rev) + url = self._geturl(path) + ### rev here should be the last history revision of the URL + fp = SelfCleanFP(cat_to_tempfile(self, path, rev, self.scratch_pool)) + lh_rev, c_rev = self._get_last_history_rev(path_parts, rev) + return fp, lh_rev + + def listdir(self, path_parts, rev, options): + path = self._getpath(path_parts) + if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check + raise vclib.Error("Path '%s' is not a directory." % _svn._norm(path)) + rev = self._getrev(rev) + entries = [] + dirents, locks = self._get_dirents(path, rev) + for name in dirents.keys(): + entry = dirents[name] + if entry.kind == _svn.svn_node_dir: + kind = vclib.DIR + elif entry.kind == _svn.svn_node_file: + kind = vclib.FILE + else: + kind = None + entries.append(vclib.DirEntry(name, kind)) + return entries + + def dirlogs(self, path_parts, rev, entries, options): + path = self._getpath(path_parts) + if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check + raise vclib.Error("Path '%s' is not a directory." % _svn._norm(path)) + rev = self._getrev(rev) + dirents, locks = self._get_dirents(path, rev) + for entry in entries: + dirent = dirents.get(entry.name, None) + # dirents is authz-sanitized, so ensure the entry is found therein. + if dirent is None: + continue + # Get authz-sanitized revision metadata. + entry.date, entry.author, entry.log, revprops, changes = \ + self._revinfo(dirent.created_rev) + entry.rev = str(dirent.created_rev) + entry.size = dirent.size + entry.lockinfo = None + if entry.name in locks: + entry.lockinfo = locks[entry.name].owner + + def itemlog(self, path_parts, rev, sortby, first, limit, options): + assert sortby == vclib.SORTBY_DEFAULT or sortby == vclib.SORTBY_REV + path_type = self.itemtype(path_parts, rev) # does auth-check + path = self._getpath(path_parts) + rev = self._getrev(rev) + url = self._geturl(path) + + # If this is a file, fetch the lock status and size (as of REV) + # for this item. + lockinfo = size_in_rev = None + if path_type == vclib.FILE: + basename = path_parts[-1] + if not isinstance(basename, bytes): + basename = basename.encode('utf-8', 'surrogateescape') + list_url = self._geturl(self._getpath(path_parts[:-1])) + dirents, locks = _svn_ra.list_directory( + list_url, rev, rev, 0, self.ctx, self.scratch_pool) + if basename in locks: + lockinfo = locks[basename].owner + if basename in dirents: + size_in_rev = dirents[basename].size + + # Special handling for the 'svn_latest_log' scenario. + ### FIXME: Don't like this hack. We should just introduce + ### something more direct in the vclib API. + if options.get('svn_latest_log', 0): + dir_lh_rev, dir_c_rev = self._get_last_history_rev(path_parts, rev) + date, author, log, revprops, changes = self._revinfo(dir_lh_rev) + return [vclib.Revision(dir_lh_rev, str(dir_lh_rev), date, author, + None, log, size_in_rev, lockinfo)] + + def _access_checker(check_path, check_rev): + return vclib.check_path_access(self, _path_parts(check_path), + path_type, check_rev) + + # It's okay if we're told to not show all logs on a file -- all + # the revisions should match correctly anyway. + lc = LogCollector(path, options.get('svn_show_all_dir_logs', 0), + lockinfo, _access_checker) + + cross_copies = options.get('svn_cross_copies', 0) + log_limit = 0 + if limit: + log_limit = first + limit + _svn_ra.client_log(url, rev, 1, log_limit, 1, + cross_copies, lc.add_log, lc, self.ctx, + self.scratch_pool) + revs = lc.logs + revs.sort() + prev = None + for rev in revs: + # Swap out revision info with stuff from the cache (which is + # authz-sanitized). + rev.date, rev.author, rev.log, revprops, changes \ + = self._revinfo(rev.number) + rev.prev = prev + prev = rev + revs.reverse() + + if len(revs) < first: + return [] + if limit: + return revs[first:first+limit] + return revs + + def itemprops(self, path_parts, rev): + path = self._getpath(path_parts) + path_type = self.itemtype(path_parts, rev) # does auth-check + rev = self._getrev(rev) + url = self._geturl(path) + return _svn_ra.simple_proplist(url, rev, self.ctx, self.scratch_pool) + + def annotate(self, path_parts, rev, include_text=False): + def _blame_cb(btn, line_no, revision, author, date, + line): + prev_rev = None + if revision > 1: + prev_rev = revision - 1 + + # If we have an invalid revision, clear the date and author + # values. Otherwise, if we have authz filtering to do, use the + # revinfo cache to do so. + if revision < 0: + date = author = None + elif self.auth: + date, author, msg, revprops, changes = self._revinfo(revision) + + # Strip text if the caller doesn't want it. + if not btn.include_text: + line = None + btn.btn.append(vclib.Annotation(line, line_no + 1, revision, prev_rev, + author, date)) + # annotate() body + path = self._getpath(path_parts) + if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check + raise vclib.Error("Path '%s' is not a file." % path) + rev = self._getrev(rev) + url = self._geturl(path) + + # Examine logs for the file to determine the oldest revision we are + # permitted to see. + log_options = { + 'svn_cross_copies' : 1, + 'svn_show_all_dir_logs' : 1, + } + revs = self.itemlog(path_parts, rev, vclib.SORTBY_REV, 0, 0, log_options) + oldest_rev = revs[-1].number + + # Now calculate the annotation data. Note that we'll not + # inherently trust the provided author and date, because authz + # rules might necessitate that we strip that information out. + blame_data = _svn._get_annotated_source( + url, rev, oldest_rev, _blame_cb, self.ctx, + include_text, self.scratch_pool) + self.scratch_pool.clear() + return blame_data, rev + + def revinfo(self, rev): + return self._revinfo(rev, 1) + + 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) + + args = vclib._diff_args(type, options) + + def _date_from_rev(rev): + date, author, msg, revprops, changes = self._revinfo(rev) + return date + + try: + temp1 = cat_to_tempfile(self, p1, r1, self.scratch_pool) + temp2 = cat_to_tempfile(self, p2, r2, self.scratch_pool) + info1 = p1, _date_from_rev(r1), r1 + info2 = p2, _date_from_rev(r2), r2 + return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args) + except _svn.SVNerr as e: + if e.get_code() == _svn.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 _svn.SVN_PROP_EXECUTABLE in props + + def filesize(self, path_parts, rev): + path = self._getpath(path_parts) + if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check + raise vclib.Error("Path '%s' is not a file." % path) + rev = self._getrev(rev) + dirents, locks = self._get_dirents(self._getpath(path_parts[:-1]), rev) + dirent = dirents.get(path_parts[-1], None) + return dirent.size + + def _getpath(self, path_parts): + return '/'.join(path_parts) + + def _getrev(self, rev): + if PY3 and isinstance(rev, bytes): + rev = rev.decode('utf-8') + if rev is None or rev == 'HEAD': + return self.youngest + try: + if isinstance(rev, str): + while rev[0] == 'r': + rev = rev[1:] + rev = int(rev) + except: + raise vclib.InvalidRevision(rev) + if (rev < 0) or (rev > self.youngest): + raise vclib.InvalidRevision(rev) + return rev + + def _geturl(self, path=None): + if not path: + return self.rootpath + path = self.rootpath + '/' + _quote(path) + return _svn.canonicalize_path(path) + + def _get_dirents(self, path, rev): + """Return a 2-type of dirents and locks, possibly reading/writing + from a local cache of that information. This functions performs + authz checks, stripping out unreadable dirents.""" + + dir_url = self._geturl(path) + path_parts = _path_parts(path) + if path: + key = str(rev) + '/' + path + else: + key = str(rev) + + # Ensure that the cache gets filled... + dirents_locks = self._dirent_cache.get(key) + if not dirents_locks: + tmp_dirents, locks = _svn_ra.list_directory(dir_url, rev, rev, 0, + self.ctx, self.scratch_pool) + dirents = {} + for name, dirent in tmp_dirents.items(): + dirent_parts = path_parts + [_svn._norm(name)] + kind = dirent.kind + if (kind == _svn.svn_node_dir or kind == _svn.svn_node_file) \ + and vclib.check_path_access(self, dirent_parts, + kind == _svn.svn_node_dir \ + and vclib.DIR or vclib.FILE, rev): + lh_rev, c_rev = self._get_last_history_rev(dirent_parts, rev) + dirent.created_rev = lh_rev + dirents[name] = dirent + dirents_locks = [dirents, locks] + self._dirent_cache[key] = dirents_locks + + # ...then return the goodies from the cache. + return dirents_locks[0], dirents_locks[1] + + def _get_last_history_rev(self, path_parts, rev): + """Return the a 2-tuple which contains: + - the last interesting revision equal to or older than REV in + the history of PATH_PARTS. + - the created_rev of of PATH_PARTS as of REV.""" + + path = self._getpath(path_parts) + url = self._geturl(self._getpath(path_parts)) + + # Get the last-changed-rev. + return _svn_ra.get_last_history_rev( + url, rev, self.ctx, self.scratch_pool) + + def _revinfo_fetch(self, rev, include_changed_paths=0): + need_changes = include_changed_paths or self.auth + revs = [] + + def _log_cb(retval, log_entry, pool): + # If Subversion happens to call us more than once, we choose not + # to care. + if retval: + return + + revision = log_entry.revision + msg, author, date, revprops = _split_revprops(log_entry.revprops) + action_map = { 'D' : vclib.DELETED, + 'A' : vclib.ADDED, + 'R' : vclib.REPLACED, + 'M' : vclib.MODIFIED, + } + + # Easy out: if we won't use the changed-path info, just return a + # changes-less tuple. + if not need_changes: + return retval.append([date, author, msg, revprops, None]) + + # Subversion 1.5 and earlier didn't offer the 'changed_paths2' + # hash, and in Subversion 1.6, it's offered but broken. + try: + changed_paths = log_entry.changed_paths2 + paths = list((changed_paths or {}).keys()) + except: + changed_paths = log_entry.changed_paths + paths = list((changed_paths or {}).keys()) + if PY3: + paths.sort(key=functools.cmp_to_key(lambda a, b: _compare_paths(a, b))) + else: + paths.sort(lambda a, b: _compare_paths(a, b)) + + # If we get this far, our caller needs changed-paths, or we need + # them for authz-related sanitization. + changes = [] + found_readable = found_unreadable = 0 + for path in paths: + change = changed_paths[path] + + # svn_log_changed_path_t (which we might get instead of the + # svn_log_changed_path2_t we'd prefer) doesn't have the + # 'node_kind' member. + pathtype = None + if hasattr(change, 'node_kind'): + if change.node_kind == _svn.svn_node_dir: + pathtype = vclib.DIR + elif change.node_kind == _svn.svn_node_file: + pathtype = vclib.FILE + + # svn_log_changed_path2_t only has the 'text_modified' and + # 'props_modified' bits in Subversion 1.7 and beyond. And + # svn_log_changed_path_t is without. + text_modified = props_modified = 0 + if hasattr(change, 'text_modified'): + if change.text_modified == _svn.svn_tristate_true: + text_modified = 1 + if hasattr(change, 'props_modified'): + if change.props_modified == _svn.svn_tristate_true: + props_modified = 1 + + # Wrong, diddily wrong wrong wrong. Can you say, + # "Manufacturing data left and right because it hurts to + # figure out the right stuff?" + action = action_map.get(change.action, vclib.MODIFIED) + if change.copyfrom_path and change.copyfrom_rev: + is_copy = 1 + base_path = change.copyfrom_path + base_rev = change.copyfrom_rev + elif action == vclib.ADDED or action == vclib.REPLACED: + is_copy = 0 + base_path = base_rev = None + else: + is_copy = 0 + base_path = path + base_rev = revision - 1 + + # Check authz rules (sadly, we have to lie about the path type) + parts = _path_parts(path) + if vclib.check_path_access(self, parts, vclib.FILE, revision): + if is_copy and base_path and (base_path != path): + parts = _path_parts(base_path) + if not vclib.check_path_access(self, parts, vclib.FILE, base_rev): + is_copy = 0 + base_path = None + base_rev = None + found_unreadable = 1 + changes.append(SVNChangedPath(path, revision, pathtype, base_path, + base_rev, action, is_copy, + text_modified, props_modified)) + found_readable = 1 + else: + found_unreadable = 1 + + # If our caller doesn't want changed-path stuff, and we have + # the info we need to make an authz determination already, + # quit this loop and get on with it. + if (not include_changed_paths) and found_unreadable and found_readable: + break + + # Filter unreadable information. + if found_unreadable: + msg = None + if not found_readable: + author = None + date = None + + # Drop unrequested changes. + if not include_changed_paths: + changes = None + + # Add this revision information to the "return" array. + retval.append([date, author, msg, revprops, changes]) + + _svn_ra.client_log(self.rootpath, rev, rev, 1, need_changes, 0, + _log_cb, revs, self.ctx, self.scratch_pool) + return tuple(revs[0]) + + def _revinfo(self, rev, include_changed_paths=0): + """Internal-use, cache-friendly revision information harvester.""" + + # Consult the revinfo cache first. If we don't have cached info, + # or our caller wants changed paths and we don't have those for + # this revision, go do the real work. + rev = self._getrev(rev) + cached_info = self._revinfo_cache.get(rev) + if not cached_info \ + or (include_changed_paths and cached_info[4] is None): + cached_info = self._revinfo_fetch(rev, include_changed_paths) + self._revinfo_cache[rev] = cached_info + return cached_info + + ##--- custom --## + + def get_youngest_revision(self): + return self.youngest + + def get_location(self, path, rev, old_rev): + if not isinstance(path, bytes): + path = path.encode('utf-8', 'surrogateescape') + try: + results = _svn_ra.svn_ra_get_locations( + self.ra_session, path, rev, [old_rev], self.scratch_pool) + except _svn.SVNerr as e: + if e.get_code() == _svn.SVN_ERR_FS_NOT_FOUND: + raise vclib.ItemNotFound(path) + raise + try: + old_path = results[old_rev] + except KeyError: + raise vclib.ItemNotFound(path) + old_path = _cleanup_path(old_path) + old_path_parts = _path_parts(old_path) + # Check access (lying about path types) + if not vclib.check_path_access(self, old_path_parts, vclib.FILE, old_rev): + raise vclib.ItemNotFound(path) + return old_path + + def created_rev(self, path, rev): + if PY3 and isinstance(path, bytes): + path = _svn._norm(path) + lh_rev, c_rev = self._get_last_history_rev(_path_parts(path), rev) + return lh_rev + + def last_rev(self, path, peg_revision, limit_revision=None): + """Given PATH, known to exist in PEG_REVISION, find the youngest + revision older than, or equal to, LIMIT_REVISION in which path + exists. Return that revision, and the path at which PATH exists in + that revision.""" + + if not isinstance(path, bytes): + path = path.encode('utf-8', 'surrogateescape') + + # Here's the plan, man. In the trivial case (where PEG_REVISION is + # the same as LIMIT_REVISION), this is a no-brainer. If + # LIMIT_REVISION is older than PEG_REVISION, we can use Subversion's + # history tracing code to find the right location. If, however, + # LIMIT_REVISION is younger than PEG_REVISION, we suffer from + # Subversion's lack of forward history searching. Our workaround, + # ugly as it may be, involves a binary search through the revisions + # between PEG_REVISION and LIMIT_REVISION to find our last live + # revision. + peg_revision = self._getrev(peg_revision) + limit_revision = self._getrev(limit_revision) + if peg_revision == limit_revision: + return peg_revision, path + elif peg_revision > limit_revision: + path = self.get_location(path, peg_revision, limit_revision) + return limit_revision, path + else: + direction = 1 + while peg_revision != limit_revision: + mid = (peg_revision + 1 + limit_revision) // 2 + try: + path = self.get_location(path, peg_revision, mid) + except vclib.ItemNotFound: + limit_revision = mid - 1 + else: + peg_revision = mid + return peg_revision, path + + def get_symlink_target(self, path_parts, rev): + """Return the target of the symbolic link versioned at PATH_PARTS + in REV, or None if that object is not a symlink.""" + + path = self._getpath(path_parts) + path_type = self.itemtype(path_parts, rev) # does auth-check + rev = self._getrev(rev) + url = self._geturl(path) + + # Symlinks must be files with the svn:special property set on them + # and with file contents which read "link SOME_PATH". + if path_type != vclib.FILE: + return None + props = _svn_ra.simple_proplist(url, rev, self.ctx, self.scratch_pool) + if _svn.SVN_PROP_SPECIAL not in props: + return None + pathspec = '' + ### FIXME: We're being a touch sloppy here, first by grabbing the + ### whole file and then by checking only the first line + ### of it. + fp = SelfCleanFP(cat_to_tempfile(self, path, rev)) + pathspec = fp.readline() + fp.close() + if pathspec[:5] != 'link ': + return None + return pathspec[5:] + diff --git a/src/lib/vclib/altsvn/svn_repos.py b/src/lib/vclib/altsvn/svn_repos.py new file mode 100644 index 00000000..6edb9c42 --- /dev/null +++ b/src/lib/vclib/altsvn/svn_repos.py @@ -0,0 +1,770 @@ +# -*-python-*- +# +# Copyright (C) 1999-2018 The ViewCVS Group. All Rights Reserved. +# +# By using this file, you agree to the terms and conditions set forth in +# the LICENSE.html file which can be found at the top level of the ViewVC +# distribution or at http://viewvc.org/license-1.html. +# +# For more information, visit http://viewvc.org/ +# +# ----------------------------------------------------------------------- + +"Version Control lib driver for locally accessible Subversion repositories" + +import vclib +import sys +import os +import os.path +import io +import tempfile +from . import _svn, _svn_repos, _path_parts, _cleanup_path,\ + _compare_paths, _split_revprops, Revision, SVNChangedPath + +### Require Subversion 1.3.1 or better. +if (_svn.SVN_VER_MAJOR, _svn.SVN_VER_MINOR, _svn.SVN_VER_PATCH) < (1, 3, 1): + raise vclib.Error("Version requirement not met (needs 1.3.1 or better)") + +PY3 = (sys.version_info[0] >= 3) + +def _allow_all(root, path, baton, pool): + """Generic authz_read_func that permits access to all paths""" + return 1 + + +def _get_last_history_rev(fsroot, path, scratch_pool=None): + history = _svn_repos.svn_fs_node_history( + fsroot, path, scratch_pool, scratch_pool) + history = _svn_repos.svn_fs_history_prev( + history, 0, scratch_pool, scratch_pool) + history_path, history_rev = _svn_repos.svn_fs_history_location( + history, scratch_pool) + return history_rev + + +def temp_checkout(svnrepos, path, rev, scratch_pool=None): + """Check out file revision to temporary file""" + fp, temp = tempfile.mkstemp() + try: + root = svnrepos._getroot(rev) + stream = _svn_repos.svn_fs_file_contents(root, path, scratch_pool) + try: + while 1: + chunk, len = _svn.svn_stream_read_full( + stream, _svn.SVN_STREAM_CHUNK_SIZE) + if not chunk: + break + os.write(fp, chunk) + finally: + _svn.svn_stream_close(stream) + finally: + os.close(fp) + return temp + +class FileContentsPipe: + def __init__(self, root, path, pool=None): + self._stream = _svn_repos.svn_fs_file_contents(root, path, pool) + self._eof = 0 + self._pool = pool + self._scratch_pool = _svn.Apr_Pool(self._pool) + + def read(self, len=None): + chunk = None + if not self._eof: + if len is None: + buffer = io.BytesIO() + try: + while 1: + hunk, len = _svn.svn_stream_read_full(self._stream, 8192) + if not hunk: + break + buffer.write(hunk) + chunk = buffer.getvalue() + finally: + buffer.close() + + else: + chunk, len = _svn.svn_stream_read_full(self._stream, len) + if not chunk: + self._eof = 1 + return chunk + + def readline(self): + chunk = None + if not self._eof: + chunk, self._eof = _svn.svn_stream_readline( + self._stream, b'\n', self._scratch_pool) + if not self._eof: + chunk = chunk + b'\n' + if not chunk: + self._eof = 1 + self._scratch_pool.clear() + return chunk + + def readlines(self): + lines = [] + while True: + line = self.readline() + if not line: + break + lines.append(line) + return lines + + def close(self): + _svn.svn_stream_close(self._stream) + del self._scratch_pool + del self._pool + return + + def eof(self): + return self._eof + + +class LocalSubversionRepository(vclib.Repository): + def __init__(self, name, rootpath, authorizer, utilities, config_dir): + if not (os.path.isdir(rootpath) \ + and os.path.isfile(os.path.join(rootpath, 'format'))): + raise vclib.ReposNotFound(name) + + # Initialize some stuff. + self.rootpath = rootpath + self.name = name + self.auth = authorizer + self.diff_cmd = utilities.diff or 'diff' + self.config_dir = config_dir or None + self.result_pool = _svn.Apr_Pool() + self.scratch_pool = _svn.Apr_Pool() + + # See if this repository is even viewable, authz-wise. + if not vclib.check_root_access(self): + raise vclib.ReposNotFound(name) + + def open(self): + # Open the repository and init some other variables. + self.repos = _svn_repos.svn_repos_open( + self.rootpath, self.result_pool, self.scratch_pool) + self.fs_ptr = _svn_repos.svn_repos_fs(self.repos) + self.youngest = _svn_repos.svn_fs_youngest_rev( + self.fs_ptr, self.scratch_pool) + self._fsroots = {} + self._revinfo_cache = {} + + # See if a universal read access determination can be made. + if self.auth and self.auth.check_universal_access(self.name) == 1: + self.auth = None + self.scratch_pool.clear() + + def rootname(self): + return self.name + + def rootpath(self): + return self.rootpath + + def roottype(self): + return vclib.SVN + + def authorizer(self): + return self.auth + + def itemtype(self, path_parts, rev): + rev = self._getrev(rev) + basepath = self._getpath(path_parts) + pathtype = self._gettype(basepath, rev) + if pathtype is None: + raise vclib.ItemNotFound(path_parts) + if not vclib.check_path_access(self, path_parts, pathtype, rev): + raise vclib.ItemNotFound(path_parts) + return pathtype + + def openfile(self, path_parts, rev, options): + path = self._getpath(path_parts) + if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check + raise vclib.Error("Path '%s' is not a file." % _svn._norm(path)) + rev = self._getrev(rev) + fsroot = self._getroot(rev) + revision = str(_get_last_history_rev(fsroot, path, self.scratch_pool)) + fp = FileContentsPipe(fsroot, path) + self.scratch_pool.clear() + return fp, revision + + def listdir(self, path_parts, rev, options): + path = self._getpath(path_parts) + if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check + raise vclib.Error("Path '%s' is not a directory." % _svn._norm(path)) + rev = self._getrev(rev) + fsroot = self._getroot(rev) + dirents = _svn_repos._listdir_helper(fsroot, path, + {_svn.svn_node_file: vclib.FILE, + _svn.svn_node_dir : vclib.DIR }, + self.scratch_pool) + entries = [ ] + for entry in dirents.values(): + if vclib.check_path_access( + self, path_parts + [_svn._norm(entry.name)], entry.kind, rev): + entries.append(vclib.DirEntry(entry.name, entry.kind)) + self.scratch_pool.clear() + return entries + + def dirlogs(self, path_parts, rev, entries, options): + path = self._getpath(path_parts) + if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check + raise vclib.Error("Path '%s' is not a directory." % _svn._norm(path)) + fsroot = self._getroot(self._getrev(rev)) + rev = self._getrev(rev) + for entry in entries: + entry_path_parts = path_parts + [_svn._norm(entry.name)] + if not vclib.check_path_access(self, entry_path_parts, entry.kind, rev): + continue + path = self._getpath(entry_path_parts) + entry_rev = _get_last_history_rev(fsroot, path, self.scratch_pool) + date, author, msg, revprops, changes = self._revinfo(entry_rev) + entry.rev = str(entry_rev) + entry.date = date + entry.author = author + entry.log = msg + if entry.kind == vclib.FILE: + entry.size = _svn_repos.svn_fs_file_length( + fsroot, path, self.scratch_pool) + lock = _svn_repos.svn_fs_get_lock(self.fs_ptr, path, self.scratch_pool) + entry.lockinfo = lock and lock.owner or None + self.scratch_pool.clear() + + def itemlog(self, path_parts, rev, sortby, first, limit, options): + """see vclib.Repository.itemlog docstring + + Option values recognized by this implementation + + 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 + """ + assert sortby == vclib.SORTBY_DEFAULT or sortby == vclib.SORTBY_REV + + path = self._getpath(path_parts) + path_type = self.itemtype(path_parts, rev) # does auth-check + rev = self._getrev(rev) + revs = [] + lockinfo = None + + # See if this path is locked. + try: + lock = _svn_repos.svn_fs_get_lock(self.fs_ptr, path, self.scratch_pool) + if lock: + lockinfo = lock.owner + except NameError: + pass + + # If our caller only wants the latest log, we'll invoke + # _log_helper for just the one revision. Otherwise, we go off + # into history-fetching mode. ### TODO: we could stand to have a + # 'limit' parameter here as numeric cut-off for the depth of our + # history search. + if options.get('svn_latest_log', 0): + revision = self._log_helper(path, rev, lockinfo) + if revision: + revision.prev = None + revs.append(revision) + else: + history = self._get_history(path, rev, path_type, first + limit, options) + if len(history) < first: + history = [] + if limit: + history = history[first:first+limit] + + for hist_rev, hist_path in history: + revision = self._log_helper(hist_path, hist_rev, lockinfo) + if revision: + # If we have unreadable copyfrom data, obscure it. + if revision.copy_path is not None: + cp_parts = _path_parts(revision.copy_path) + if not vclib.check_path_access(self, cp_parts, path_type, + revision.copy_rev): + revision.copy_path = revision.copy_rev = None + revision.prev = None + if len(revs): + revs[-1].prev = revision + revs.append(revision) + self.scratch_pool.clear() + return revs + + def itemprops(self, path_parts, rev): + path = self._getpath(path_parts) + path_type = self.itemtype(path_parts, rev) # does auth-check + rev = self._getrev(rev) + fsroot = self._getroot(rev) + return _svn_repos.svn_fs_node_proplist(fsroot, path, self.scratch_pool) + + def annotate(self, path_parts, rev, include_text=False): + def _blame_cb(btn, line_no, rev, author, date, text): + prev_rev = None + if rev > btn.first_rev: + prev_rev = rev - 1 + if not btn.include_text: + text = None + btn.btn.append(vclib.Annotation(text, line_no + 1, rev, + prev_rev, author, date)) + # annotate() body + path = self._getpath(path_parts) + path_type = self.itemtype(path_parts, rev) # does auth-check + if path_type != vclib.FILE: + raise vclib.Error("Path '%s' is not a file." % _svn._norm(path)) + rev = self._getrev(rev) + fsroot = self._getroot(rev) + history = self._get_history(path, rev, path_type, 0, + {'svn_cross_copies': 1}) + youngest_rev, youngest_path = history[0] + oldest_rev, oldest_path = history[-1] + ctx = _svn.setup_client_ctx(self.config_dir, self.scratch_pool) + source = _svn._get_annotated_source( + _svn.rootpath2url(self.rootpath, path), youngest_rev, + oldest_rev, _blame_cb, ctx, include_text, + self.scratch_pool) + del ctx + self.scratch_pool.clear() + return source, youngest_rev + + def revinfo(self, rev): + return self._revinfo(rev, 1) + + 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) + + args = vclib._diff_args(type, options) + + def _date_from_rev(rev): + date, author, msg, revprops, 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 + return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args) + except _svn.SVNerr as e: + if e.get_code() == _svn.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 _svn.SVN_PROP_EXECUTABLE in props + + def filesize(self, path_parts, rev): + path = self._getpath(path_parts) + if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check + raise vclib.Error("Path '%s' is not a file." % _svn._norm(path)) + fsroot = self._getroot(self._getrev(rev)) + return _svn_repos.svn_fs_file_length(fsroot, path, self.scratch_pool) + + ##--- helpers ---## + + def _revinfo(self, rev, include_changed_paths=0): + """Internal-use, cache-friendly revision information harvester.""" + + def _get_changed_paths(fsroot): + """Return a 3-tuple: found_readable, found_unreadable, changed_paths.""" + changedpaths = {} + changes = _svn_repos._get_changed_paths_helper( + self.fs_ptr, fsroot, self.scratch_pool) + + # Copy the Subversion changes into a new hash, checking + # authorization and converting them into ChangedPath objects. + found_readable = found_unreadable = 0 + for path in changes.keys(): + change = changes[path] + if change.path: + change.path = _cleanup_path(change.path) + if change.base_path: + change.base_path = _cleanup_path(change.base_path) + is_copy = 0 + if change.action == _svn_repos.svn_fs_path_change_add: + action = vclib.ADDED + elif change.action == _svn_repos.svn_fs_path_change_delete: + action = vclib.DELETED + elif change.action == _svn_repos.svn_fs_path_change_replace: + action = vclib.REPLACED + else: + action = vclib.MODIFIED + if (action == vclib.ADDED or action == vclib.REPLACED) \ + and change.base_path \ + and change.base_rev: + is_copy = 1 + if change.item_kind == _svn.svn_node_dir: + pathtype = vclib.DIR + elif change.item_kind == _svn.svn_node_file: + pathtype = vclib.FILE + else: + pathtype = None + + parts = _path_parts(path) + if vclib.check_path_access(self, parts, pathtype, rev): + if is_copy and change.base_path and (change.base_path != path): + parts = _path_parts(change.base_path) + if not vclib.check_path_access(self, parts, pathtype, + change.base_rev): + is_copy = 0 + change.base_path = None + change.base_rev = None + found_unreadable = 1 + changedpaths[path] = SVNChangedPath(path, rev, pathtype, + change.base_path, + change.base_rev, action, + is_copy, change.text_changed, + change.prop_changes) + found_readable = 1 + else: + found_unreadable = 1 + if PY3: + return found_readable, found_unreadable, list(changedpaths.values()) + else: + return found_readable, found_unreadable, changedpaths.values() + + def _get_change_copyinfo(fsroot, path, change): + # If we know the copyfrom info, return it... + if hasattr(change, 'copyfrom_known') and change.copyfrom_known: + copyfrom_path = change.copyfrom_path + copyfrom_rev = change.copyfrom_rev + # ...otherwise, if this change could be a copy (that is, it + # contains an add action), query the copyfrom info ... + elif (change.change_kind == _svn_repos.svn_fs_path_change_add or + change.change_kind == _svn_repos.svn_fs_path_change_replace): + copyfrom_rev, copyfrom_path = _svn_repos.svn_fs_copied_from( + fsroot, path, self.scratch_pool) + # ...else, there's no copyfrom info. + else: + copyfrom_rev = _svn.SVN_INVALID_REVNUM + copyfrom_path = None + return copyfrom_path, copyfrom_rev + + def _simple_auth_check(fsroot): + """Return a 2-tuple: found_readable, found_unreadable.""" + found_unreadable = found_readable = 0 + changes = _svn_repos.svn_fs_paths_changed(fsroot, self.scratch_pool) + paths = changes.keys() + for path in paths: + change = changes[path] + pathtype = None + if hasattr(change, 'node_kind'): + if change.node_kind == _svn.svn_node_file: + pathtype = vclib.FILE + elif change.node_kind == _svn.svn_node_dir: + pathtype = vclib.DIR + parts = _path_parts(path) + if pathtype is None: + # Figure out the pathtype so we can query the authz subsystem. + if change.change_kind == _svn_repos.svn_fs_path_change_delete: + # Deletions are annoying, because they might be underneath + # copies (make their previous location non-trivial). + prev_parts = parts + prev_rev = rev - 1 + parent_parts = parts[:-1] + while parent_parts: + parent_path = '/' + self._getpath(parent_parts) + parent_change = changes.get(parent_path) + if not (parent_change and \ + (parent_change.change_kind in + (_svn_repos.svn_fs_path_change_add, + _svn_repos.svn_fs_path_change_replace))): + del(parent_parts[-1]) + continue + copyfrom_path, copyfrom_rev = \ + _get_change_copyinfo(fsroot, parent_path, parent_change) + if copyfrom_path: + prev_rev = copyfrom_rev + prev_parts = _path_parts(copyfrom_path) + \ + parts[len(parent_parts):] + break + del(parent_parts[-1]) + pathtype = self._gettype(self._getpath(prev_parts), prev_rev) + else: + pathtype = self._gettype(self._getpath(parts), rev) + if vclib.check_path_access(self, parts, pathtype, rev): + found_readable = 1 + copyfrom_path, copyfrom_rev = \ + _get_change_copyinfo(fsroot, path, change) + if copyfrom_path and copyfrom_path != path: + parts = _path_parts(copyfrom_path) + if not vclib.check_path_access(self, parts, pathtype, + copyfrom_rev): + found_unreadable = 1 + else: + found_unreadable = 1 + if found_readable and found_unreadable: + break + return found_readable, found_unreadable + + def _revinfo_helper(rev, include_changed_paths): + # Get the revision property info. (Would use + # editor.get_root_props(), but something is broken there...) + revprops = _svn_repos.svn_fs_revision_proplist( + self.fs_ptr, rev, self.scratch_pool) + msg, author, date, revprops = _split_revprops(revprops) + + # Optimization: If our caller doesn't care about the changed + # paths, and we don't need them to do authz determinations, let's + # get outta here. + if self.auth is None and not include_changed_paths: + return date, author, msg, revprops, None + + # If we get here, then we either need the changed paths because we + # were asked for them, or we need them to do authorization checks. + # + # If we only need them for authorization checks, though, we + # won't bother generating fully populated ChangedPath items (the + # cost is too great). + fsroot = self._getroot(rev) + if include_changed_paths: + found_readable, found_unreadable, changedpaths = \ + _get_changed_paths(fsroot) + else: + changedpaths = None + found_readable, found_unreadable = _simple_auth_check(fsroot) + + # Filter our metadata where necessary, and return the requested data. + if found_unreadable: + msg = None + if not found_readable: + author = None + date = None + return date, author, msg, revprops, changedpaths + + # Consult the revinfo cache first. If we don't have cached info, + # or our caller wants changed paths and we don't have those for + # this revision, go do the real work. + rev = self._getrev(rev) + cached_info = self._revinfo_cache.get(rev) + if not cached_info \ + or (include_changed_paths and cached_info[4] is None): + cached_info = _revinfo_helper(rev, include_changed_paths) + self._revinfo_cache[rev] = cached_info + self.scratch_pool.clear() + return tuple(cached_info) + + def _log_helper(self, path, rev, lockinfo): + # Though rev_root alives this frame only, passing self.scratch_pool + # to _svn_repos.svn_fs_revision_root() causes crash in + # _svn_repos.svn_fs_file_length() below. There seems to exist pool + # management bug around there... + rev_root = _svn_repos.svn_fs_revision_root( + self.fs_ptr, rev, self.result_pool) + copyfrom_rev, copyfrom_path = _svn_repos.svn_fs_copied_from( + rev_root, path, self.scratch_pool) + date, author, msg, revprops, changes = self._revinfo(rev) + if _svn_repos.svn_fs_is_file(rev_root, path, self.scratch_pool): + size = _svn_repos.svn_fs_file_length(rev_root, path, self.scratch_pool) + else: + size = None + return Revision(rev, date, author, msg, size, lockinfo, path, + copyfrom_path and _cleanup_path(copyfrom_path), + copyfrom_rev) + + def _get_history(self, path, rev, path_type, limit=0, options={}): + if self.youngest == 0: + return [] + + rev_paths = [] + fsroot = self._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 = _svn_repos.svn_fs_check_path(fsroot, path, self.scratch_pool) + if kind is _svn.svn_node_file: + show_all_logs = 1 + + # Instantiate a NodeHistory collector object, and use it to collect + # history items for PATH@REV. + try: + history = _svn_repos._get_history_helper( + self.fs_ptr, path, rev, + options.get('svn_cross_copies', 0), + show_all_logs, limit, self.scratch_pool) + except _svn.SVNerr as e: + if e.get_code() != _svn.SVN_ERR_CEASE_INVOCATION: + raise + + # Now, iterate over those history items, checking for changes of + # location, pruning as necessitated by authz rules. + for hist_rev, hist_path in history: + path_parts = _path_parts(hist_path) + if not vclib.check_path_access(self, path_parts, path_type, hist_rev): + break + rev_paths.append([hist_rev, hist_path]) + return rev_paths + + def _getpath(self, path_parts): + return '/'.join(path_parts) + + def _getrev(self, rev): + if PY3 and isinstance(rev, bytes): + rev = rev.decode('utf-8') + if rev is None or rev == 'HEAD': + return self.youngest + try: + if isinstance(rev, str): + while rev[0:1] == 'r': + rev = rev[1:] + rev = int(rev) + except: + raise vclib.InvalidRevision(rev) + if (rev < 0) or (rev > self.youngest): + raise vclib.InvalidRevision(rev) + return rev + + def _getroot(self, rev): + return self.fs_ptr._getroot(rev) + + def _gettype(self, path, rev): + # Similar to itemtype(), but without the authz check. Returns + # None for missing paths. + try: + kind = _svn_repos.svn_fs_check_path( + self._getroot(rev), path, self.scratch_pool) + except: + return None + if kind == _svn.svn_node_dir: + return vclib.DIR + if kind == _svn.svn_node_file: + return vclib.FILE + return None + + ##--- custom ---## + + def get_youngest_revision(self): + return self.youngest + + def get_location(self, path, rev, old_rev): + if not isinstance(path, bytes): + path = path.encode('utf-8', 'surrogateescape') + try: + results = _svn_repos.svn_repos_trace_node_locations( + self.fs_ptr, path, rev, [old_rev], _allow_all, None) + except _svn.SVNerr as e: + if e.get_code() == _svn.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(self, full_name, rev): + if not isinstance(full_name, bytes): + full_name = full_name.encode('utf-8', 'surrogateescape') + return _svn_repos.svn_fs_node_created_rev( + self._getroot(rev), full_name, self.scratch_pool) + + def last_rev(self, path, peg_revision, limit_revision=None): + """Given PATH, known to exist in PEG_REVISION, find the youngest + revision older than, or equal to, LIMIT_REVISION in which path + exists. Return that revision, and the path at which PATH exists in + that revision.""" + + if not isinstance(path, bytes): + path = path.encode('utf-8', 'surrogateescape') + + # Here's the plan, man. In the trivial case (where PEG_REVISION is + # the same as LIMIT_REVISION), this is a no-brainer. If + # LIMIT_REVISION is older than PEG_REVISION, we can use Subversion's + # history tracing code to find the right location. If, however, + # LIMIT_REVISION is younger than PEG_REVISION, we suffer from + # Subversion's lack of forward history searching. Our workaround, + # ugly as it may be, involves a binary search through the revisions + # between PEG_REVISION and LIMIT_REVISION to find our last live + # revision. + peg_revision = self._getrev(peg_revision) + limit_revision = self._getrev(limit_revision) + try: + if peg_revision == limit_revision: + return peg_revision, path + elif peg_revision > limit_revision: + fsroot = self._getroot(peg_revision) + history = _svn_repos.svn_fs_node_history( + fsroot, path, self.scratch_pool, self.scratch_pool) + while history: + path, peg_revision = _svn_repos.svn_fs_history_location(history) + if peg_revision <= limit_revision: + return max(peg_revision, limit_revision), _cleanup_path(path) + history = _svn_repos.svn_fs_history_prev(history, 1) + del history + self.scratch_pool.clear() + return peg_revision, _cleanup_path(path) + else: + orig_id = _svn_repos.svn_fs_node_id( + self._getroot(peg_revision), path, self.scratch_pool) + mid_id = None + try: + while peg_revision != limit_revision: + mid = (peg_revision + 1 + limit_revision) // 2 + try: + mid_id = _svn_repos.svn_fs_node_id( + self._getroot(mid), path, self.scratch_pool) + except _svn.SVNerr as e: + if e.get_code() == _svn.SVN_ERR_FS_NOT_FOUND: + cmp = -1 + else: + raise + else: + ### Not quite right. Need a comparison function that only returns + ### true when the two nodes are the same copy, not just related. + cmp = _svn_repos.svn_fs_compare_ids(orig_id, mid_id) + + if cmp in (0, 1): + peg_revision = mid + else: + limit_revision = mid - 1 + finally: + del mid_id + del orig_id + self.scratch_pool.clear() + return peg_revision, path + finally: + pass + + def get_symlink_target(self, path_parts, rev): + """Return the target of the symbolic link versioned at PATH_PARTS + in REV, or None if that object is not a symlink.""" + + path = self._getpath(path_parts) + rev = self._getrev(rev) + path_type = self.itemtype(path_parts, rev) # does auth-check + fsroot = self._getroot(rev) + + # Symlinks must be files with the svn:special property set on them + # and with file contents which read "link SOME_PATH". + if path_type != vclib.FILE: + return None + props = _svn_repos.svn_fs_node_proplist(fsroot, path, self.scratch_pool) + if _svn.SVN_PROP_SPECIAL not in props: + return None + pathspec = b'' + ### FIXME: We're being a touch sloppy here, only checking the first line + ### of the file. + stream = _svn_repos.svn_fs_file_contents(fsroot, path, self.scratch_pool) + try: + pathspec, eof = _svn.svn_stream_readline(stream, b'\n') + finally: + _svn.svn_stream_close(stream) + del stream + self.scratch_pool.clear() + if pathspec[:5] != b'link ': + return None + return _svn._norm(pathspec[5:]) +