This patch fixes a bunch of issues with the Python bindings. These changes are required for the Subversion integration into Bazaar, see http://bazaar-vcs.org/BzrSvn. The changes are all pulled from Subversion trunk (which will become 1.5). === added file 'subversion/bindings/swig/python/tests/client.py' --- /dev/null +++ subversion/bindings/swig/python/tests/client.py @@ -0,0 +1,111 @@ +import unittest, os, tempfile, types + +from svn import core, repos, fs, delta, client, wc + +from trac.versioncontrol.tests.svn_fs import SubversionRepositoryTestSetup, \ + REPOS_PATH +from urllib import pathname2url + +class SubversionRepositoryTestCase(unittest.TestCase): + """Test cases for the Subversion repository layer""" + + def setUp(self): + """Load a Subversion repository""" + self.client_ctx = client.svn_client_create_context() + + providers = [ + client.svn_client_get_simple_provider(), + client.svn_client_get_username_provider(), + ] + + self.client_ctx.auth_baton = core.svn_auth_open(providers) + self.repos_url = "file://" + pathname2url(REPOS_PATH) + + def info_receiver(self, path, info, pool): + """Squirrel away the output from 'svn info' so that the unit tests + can get at them.""" + self.path = path + self.info = info + + def test_checkout(self): + """Test svn_client_checkout2.""" + + rev = core.svn_opt_revision_t() + rev.kind = core.svn_opt_revision_head + + path = os.path.join(tempfile.gettempdir(), 'checkout') + + self.assertRaises(ValueError, client.checkout2, + self.repos_url, path, None, None, True, True, + self.client_ctx) + + client.checkout2(self.repos_url, path, rev, rev, True, True, + self.client_ctx) + + def test_info(self): + """Test scope of get_logs callbacks""" + + # Run info + revt = core.svn_opt_revision_t() + revt.kind = core.svn_opt_revision_head + repos_url = "file://" + pathname2url(REPOS_PATH) + client.info(repos_url, revt, revt, self.info_receiver, + False, self.client_ctx) + + # Check output from running info. This also serves to verify that + # the internal 'info' object is still valid + self.assertEqual(self.path, os.path.basename(REPOS_PATH)) + self.info.assert_valid() + self.assertEqual(self.info.URL, repos_url) + self.assertEqual(self.info.repos_root_URL, repos_url) + + + def test_uuid_from_url(self): + """Test svn_client_uuid_from_url on a file:// URL""" + self.assert_(isinstance( + client.uuid_from_url(self.repos_url, self.client_ctx), + types.StringTypes)) + + def test_url_from_path(self): + """Test svn_client_url_from_path for a file:// URL""" + self.assertEquals(client.url_from_path(self.repos_url), self.repos_url) + + rev = core.svn_opt_revision_t() + rev.kind = core.svn_opt_revision_head + + path = os.path.join(tempfile.gettempdir(), 'url_from_path') + + client.checkout2(self.repos_url, path, rev, rev, True, True, + self.client_ctx) + + self.assertEquals(client.url_from_path(path), self.repos_url) + + def test_uuid_from_path(self): + """Test svn_client_uuid_from_path.""" + rev = core.svn_opt_revision_t() + rev.kind = core.svn_opt_revision_head + + path = os.path.join(tempfile.gettempdir(), 'uuid_from_path') + + client.checkout2(self.repos_url, path, rev, rev, True, True, + self.client_ctx) + + wc_adm = wc.adm_open3(None, path, False, 0, None) + + self.assertEquals(client.uuid_from_path(path, wc_adm, self.client_ctx), + client.uuid_from_url(self.repos_url, self.client_ctx)) + + self.assert_(isinstance(client.uuid_from_path(path, wc_adm, + self.client_ctx), types.StringTypes)) + + def test_open_ra_session(self): + """Test svn_client_open_ra_session().""" + client.open_ra_session(self.repos_url, self.client_ctx) + +def suite(): + return unittest.makeSuite(SubversionRepositoryTestCase, 'test', + suiteClass=SubversionRepositoryTestSetup) + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + runner.run(suite()) === added file 'subversion/bindings/swig/python/tests/ra.py' --- /dev/null +++ subversion/bindings/swig/python/tests/ra.py @@ -0,0 +1,136 @@ +import unittest, os + +from svn import core, repos, fs, delta, client, ra + +from trac.versioncontrol.tests.svn_fs import SubversionRepositoryTestSetup, \ + REPOS_PATH +from urllib import pathname2url + +class SubversionRepositoryTestCase(unittest.TestCase): + """Test cases for the Subversion repository layer""" + + def setUp(self): + """Load a Subversion repository""" + + ra.initialize() + + self.repos_url = "file://" + pathname2url(REPOS_PATH) + + # Open repository directly for cross-checking + self.repos = repos.open(REPOS_PATH) + self.fs = repos.fs(self.repos) + + callbacks = ra.callbacks2_t() + + self.ra_ctx = ra.open2(self.repos_url, callbacks, None, None) + + def test_get_repos_root(self): + root = ra.get_repos_root(self.ra_ctx) + self.assertEqual(root,self.repos_url) + + def test_get_uuid(self): + ra_uuid = ra.get_uuid(self.ra_ctx) + fs_uuid = fs.get_uuid(self.fs) + self.assertEqual(ra_uuid,fs_uuid) + + def test_get_lastest_revnum(self): + ra_revnum = ra.get_latest_revnum(self.ra_ctx) + fs_revnum = fs.youngest_rev(self.fs) + self.assertEqual(ra_revnum,fs_revnum) + + def test_get_dir(self): + (dirents,_,props) = ra.get_dir(self.ra_ctx, '', 1) + self.assertTrue(dirents.has_key('trunk')) + self.assertTrue(dirents.has_key('branches')) + self.assertTrue(dirents.has_key('tags')) + self.assertEqual(dirents['trunk'].kind, core.svn_node_dir) + self.assertEqual(dirents['branches'].kind, core.svn_node_dir) + self.assertEqual(dirents['tags'].kind, core.svn_node_dir) + self.assertTrue(props.has_key(core.SVN_PROP_ENTRY_UUID)) + self.assertTrue(props.has_key(core.SVN_PROP_ENTRY_LAST_AUTHOR)) + + (dirents,_,_) = ra.get_dir(self.ra_ctx, 'trunk', 1) + + self.assertEqual(dirents, {}) + + (dirents,_,_) = ra.get_dir(self.ra_ctx, 'trunk', 10) + + self.assertTrue(dirents.has_key('README2.txt')) + self.assertEqual(dirents['README2.txt'].kind,core.svn_node_file) + + def test_commit(self): + def my_callback(revision, date, author, baton): + self.assertEqual(info.revision, fs.youngest_rev(self.fs)) + + editor, edit_baton = ra.get_commit_editor(self.ra_ctx, "foobar", my_callback, None, False) + root = delta.editor_invoke_open_root(editor, edit_baton, 4) + child = delta.editor_invoke_add_directory(editor, "bla", root, None, 0) + delta.editor_invoke_close_edit(editor, edit_baton) + + def test_commit(self): + def my_callback(revision, date, author): + self.assertEqual(revision, fs.youngest_rev(self.fs)) + + editor, edit_baton = ra.get_commit_editor(self.ra_ctx, "foobar", my_callback, None, False) + root = delta.editor_invoke_open_root(editor, edit_baton, 4) + child = delta.editor_invoke_add_directory(editor, "blah", root, None, 0) + delta.editor_invoke_close_edit(editor, edit_baton) + + def test_commit(self): + def my_callback(revision, date, author): + self.assertEqual(revision, fs.youngest_rev(self.fs)) + + editor, edit_baton = ra.get_commit_editor(self.ra_ctx, "foobar", my_callback, None, False) + root = delta.editor_invoke_open_root(editor, edit_baton, 4) + child = delta.editor_invoke_add_directory(editor, "blah", root, None, 0) + delta.editor_invoke_close_edit(editor, edit_baton) + + def test_get_locations(self): + locations = ra.get_locations(self.ra_ctx, "/trunk/README.txt", 2, range(1,5)) + self.assertEqual(locations, { + 2: '/trunk/README.txt', + 3: '/trunk/README.txt', + 4: '/trunk/README.txt'}) + + def test_get_file_revs(self): + def rev_handler(path, rev, rev_props, prop_diffs, pool): + self.assert_(rev == 2 or rev == 3) + self.assertEqual(path, "/trunk/README.txt") + if rev == 2: + self.assertEqual(rev_props, { + 'svn:log': 'Added README.', + 'svn:author': 'john', + 'svn:date': '2005-04-01T13:12:18.216267Z' + }) + self.assertEqual(prop_diffs, {}) + elif rev == 3: + self.assertEqual(rev_props, { + 'svn:log': 'Fixed README.\n', + 'svn:author': 'kate', + 'svn:date': '2005-04-01T13:24:58.234643Z' + }) + self.assertEqual(prop_diffs, {'svn:mime-type': 'text/plain', 'svn:eol-style': 'native'}) + + ra.get_file_revs(self.ra_ctx, "trunk/README.txt", 0, 10, rev_handler) + + def test_update(self): + class TestEditor(delta.Editor): + pass + + editor = TestEditor() + + e_ptr, e_baton = delta.make_editor(editor) + + reporter, reporter_baton = ra.do_update(self.ra_ctx, 10, "", True, e_ptr, e_baton) + + ra.reporter2_invoke_set_path(reporter, reporter_baton, "", 0, True, None) + + ra.reporter2_invoke_finish_report(reporter, reporter_baton) + +def suite(): + return unittest.makeSuite(SubversionRepositoryTestCase, 'test', + suiteClass=SubversionRepositoryTestSetup) + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + runner.run(suite()) === added file 'subversion/bindings/swig/python/tests/wc.py' --- /dev/null +++ subversion/bindings/swig/python/tests/wc.py @@ -0,0 +1,175 @@ +import unittest, os, tempfile +import shutil + +from svn import core, repos, wc, client +from libsvn.core import SubversionException + +from trac.versioncontrol.tests.svn_fs import SubversionRepositoryTestSetup, \ + REPOS_PATH +from urllib import pathname2url + +class SubversionRepositoryTestCase(unittest.TestCase): + """Test cases for the Subversion working copy layer""" + + def setUp(self): + """Load a Subversion repository""" + + self.repos_url = "file://" + pathname2url(REPOS_PATH) + + # Open repository directly for cross-checking + self.repos = repos.open(REPOS_PATH) + self.fs = repos.fs(self.repos) + + self.path = tempfile.mktemp() + + client_ctx = client.create_context() + + rev = core.svn_opt_revision_t() + rev.kind = core.svn_opt_revision_head + + client.checkout2(self.repos_url, self.path, rev, rev, True, True, + client_ctx) + + self.wc = wc.adm_open3(None, self.path, True, -1, None) + + def test_entry(self): + wc_entry = wc.entry(self.path, self.wc, True) + + def test_lock(self): + lock = wc.add_lock(self.path, core.svn_lock_create(core.Pool()), self.wc) + self.assertEqual(True, wc.adm_locked(self.wc)) + self.assertEqual(True, wc.locked(self.path)) + wc.remove_lock(self.path, self.wc) + + def test_version(self): + wc.version() + + def test_access_path(self): + self.assertEqual(self.path, wc.adm_access_path(self.wc)) + + def test_is_adm_dir(self): + self.assertTrue(wc.is_adm_dir(".svn")) + self.assertFalse(wc.is_adm_dir(".foosvn")) + + def test_get_adm_dir(self): + self.assertTrue(isinstance(wc.get_adm_dir(), basestring)) + + def test_set_adm_dir(self): + self.assertRaises(SubversionException, wc.set_adm_dir, ".foobar") + self.assertTrue(wc.is_adm_dir(".svn")) + self.assertFalse(wc.is_adm_dir("_svn")) + self.assertFalse(wc.is_adm_dir(".foobar")) + wc.set_adm_dir("_svn") + self.assertTrue(wc.is_adm_dir("_svn")) + self.assertEqual("_svn", wc.get_adm_dir()) + wc.set_adm_dir(".svn") + self.assertFalse(wc.is_adm_dir("_svn")) + self.assertEqual(".svn", wc.get_adm_dir()) + + def test_init_traversal_info(self): + wc.init_traversal_info() + + def test_crawl_revisions2(self): + infos = [] + set_paths = [] + + def notify(info, pool): + infos.append(info) + + class MyReporter: + def __init__(self): + self._finished_report = False + + def abort_report(self, pool): + pass + + def finish_report(self, pool): + self._finished_report = True + + def set_path(self, path, revision, start_empty, lock_token, pool): + set_paths.append(path) + + def link_path(self, path, url, revision, start_empty, lock_token, + pool): + pass + + def delete_path(self, path, pool): + pass + + # Remove trunk/README.txt + readme_path = os.path.join(self.path, "trunk", "README.txt") + self.assert_(os.path.exists(readme_path)) + os.remove(readme_path) + + # Restore trunk/README.txt using crawl_revision2 + info = wc.init_traversal_info() + reporter = MyReporter() + wc.crawl_revisions2(self.path, self.wc, reporter, + True, True, False, notify, info) + + # Check that the report finished + self.assert_(reporter._finished_report) + self.assertEqual([''], set_paths) + self.assertEqual(1, len(infos)) + + # Check content of infos object + [info] = infos + self.assertEqual(readme_path, info.path) + self.assertEqual(core.svn_node_file, info.kind) + self.assertEqual(-1, info.revision) + + def test_create_notify(self): + wc.create_notify(self.path, wc.notify_add) + + def test_check_wc(self): + self.assertTrue(wc.check_wc(self.path) > 0) + + def test_get_ancestry(self): + self.assertEqual([self.repos_url, 12], + wc.get_ancestry(self.path, self.wc)) + + def test_status(self): + wc.status2(self.path, self.wc) + + def test_is_normal_prop(self): + self.failIf(wc.is_normal_prop('svn:wc:foo:bar')) + self.failIf(wc.is_normal_prop('svn:entry:foo:bar')) + self.assert_(wc.is_normal_prop('svn:foo:bar')) + self.assert_(wc.is_normal_prop('foreign:foo:bar')) + + def test_is_wc_prop(self): + self.assert_(wc.is_wc_prop('svn:wc:foo:bar')) + self.failIf(wc.is_wc_prop('svn:entry:foo:bar')) + self.failIf(wc.is_wc_prop('svn:foo:bar')) + self.failIf(wc.is_wc_prop('foreign:foo:bar')) + + def test_is_entry_prop(self): + self.assert_(wc.is_entry_prop('svn:entry:foo:bar')) + self.failIf(wc.is_entry_prop('svn:wc:foo:bar')) + self.failIf(wc.is_entry_prop('svn:foo:bar')) + self.failIf(wc.is_entry_prop('foreign:foo:bar')) + + def test_get_pristine_copy_path(self): + self.assertEqual( + wc.get_pristine_copy_path(os.path.join(self.path, 'foo')), + os.path.join(self.path, wc.get_adm_dir(), 'text-base', 'foo.svn-base')) + + def test_get_ignores(self): + self.assert_(isinstance(wc.get_ignores(None, self.wc), list)) + + def test_entries_read(self): + entries = wc.entries_read(self.wc, True) + + self.assertEqual(['', 'tags', 'branches', 'trunk'], entries.keys()) + + def tearDown(self): + wc.adm_close(self.wc) + shutil.rmtree(self.path) + +def suite(): + return unittest.makeSuite(SubversionRepositoryTestCase, 'test', + suiteClass=SubversionRepositoryTestSetup) + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + runner.run(suite()) === modified file 'subversion/bindings/swig/include/apr.swg' --- subversion/bindings/swig/include/apr.swg +++ subversion/bindings/swig/include/apr.swg @@ -146,14 +146,24 @@ /* ----------------------------------------------------------------------- create an OUTPUT argument defn for an apr_hash_t ** which is storing + dirent values +*/ +%typemap(python,in,numinputs=0) apr_hash_t **DIRENTHASH = apr_hash_t **OUTPUT; +%typemap(python,argout,fragment="t_output_helper") apr_hash_t **DIRENTHASH { + $result = t_output_helper($result, + svn_swig_py_convert_hash(*$1, + SWIGTYPE_p_svn_dirent_t, + NULL)); +} + +/* ----------------------------------------------------------------------- + create an OUTPUT argument defn for an apr_hash_t ** which is storing property values */ %typemap(python,in,numinputs=0) apr_hash_t **PROPHASH = apr_hash_t **OUTPUT; -%typemap(python,argout) apr_hash_t **PROPHASH { - /* toss prior result, get new result from the hash */ - Py_DECREF($result); - $result = svn_swig_py_prophash_to_dict(*$1); +%typemap(python,argout,fragment="t_output_helper") apr_hash_t **PROPHASH { + $result = t_output_helper($result, svn_swig_py_prophash_to_dict(*$1)); } %typemap(perl5,in,numinputs=0) apr_hash_t **PROPHASH = apr_hash_t **OUTPUT; === modified file 'subversion/bindings/swig/include/proxy_apr.swg' --- subversion/bindings/swig/include/proxy_apr.swg +++ subversion/bindings/swig/include/proxy_apr.swg @@ -112,6 +112,28 @@ del self._parent_pool if hasattr(self, "_is_valid"): del self._is_valid + + # Clear out any pool-owned references inserted by typemaps + if hasattr(self, "_owned_refs"): + del self._owned_refs + + def _add_owned_ref(self, ref): + """Add a new 'owned' reference -- i.e. a Python object contained in a C + structure allocated in this pool. Used by the typemaps to manage + reference counting semantics.""" + if not hasattr(self, "_owned_refs"): + self._owned_refs = {} + if self._owned_refs.has_key(ref): + self._owned_refs[ref] += 1 + else: + self._owned_refs[ref] = 1 + + def _remove_owned_ref(self, ref): + """Remove an existing 'owned' reference. Also used by typemaps.""" + if hasattr(self, "_owned_refs") and self._owned_refs.has_key(ref): + self._owned_refs[ref] -= 1 + if self._owned_refs[ref] == 0: + del self._owned_refs[ref] def __del__(self): """Automatically destroy memory pools, if necessary""" === modified file 'subversion/bindings/swig/include/svn_types.swg' --- subversion/bindings/swig/include/svn_types.swg +++ subversion/bindings/swig/include/svn_types.swg @@ -430,6 +430,12 @@ $2 = (void *)svn_swig_rb_make_baton($input, _global_svn_swig_rb_pool); }; +%typemap(python, in) (svn_commit_callback2_t callback, void *callback_baton) +{ + $1 = svn_swig_py_commit_callback2; + $2 = (void *)$input; +} + /* ----------------------------------------------------------------------- Callback: svn_cancel_func_t */ @@ -493,6 +499,41 @@ $1 = svn_swig_rb_make_stream($input); } +#ifdef SWIGPYTHON +%typemap(in) (svn_commit_callback_t callback, void *callback_baton) +{ + $1 = svn_swig_py_commit_callback; + $2 = (void *)$input; +} +#endif + +#ifdef SWIGPYTHON +%typemap(in) (svn_commit_callback_t callback, void *callback_baton) +{ + $1 = svn_swig_py_commit_callback; + $2 = (void *)$input; +} +#endif + +/* ----------------------------------------------------------------------- + Mapper to automatically turn Python objects into void* batons on assignment +*/ + +#ifdef SWIGPYTHON +%typemap(in) void *PY_AS_VOID (PyObject *newRef) { + newRef = $input; + if ($input == Py_None) { + $1 = newRef = NULL; + } else { + newRef = $input; + $1 = (void *)$input; + } + if (svn_swig_py_pool_set_owned_ref(obj0, (PyObject *)arg1->$1_name, newRef)) { + SWIG_fail; + } +} +#endif + /* ----------------------------------------------------------------------- Wrap the digest output for functions populating digests. */ === modified file 'subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c' --- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c +++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c @@ -30,6 +30,7 @@ #include #include +#include "svn_client.h" #include "svn_string.h" #include "svn_opt.h" #include "svn_delta.h" @@ -485,6 +486,51 @@ return convert_hash(hash, convert_svn_string_t, NULL, NULL); } +static PyObject *proparray_to_dict(const apr_array_header_t *array) +{ + PyObject *dict = PyDict_New(); + int i; + + if (dict == NULL) + return NULL; + + for (i = 0; i < array->nelts; ++i) + { + svn_prop_t prop; + PyObject *py_key, *py_value; + + prop = APR_ARRAY_IDX(array, i, svn_prop_t); + + py_key = PyString_FromString(prop.name); + if (py_key == NULL) + goto error; + + if (prop.value == NULL) + { + py_value = Py_None; + Py_INCREF(Py_None); + } + else + { + py_value = PyString_FromStringAndSize((void *)prop.value->data, + prop.value->len); + if (py_value == NULL) { + Py_DECREF(py_key); + goto error; + } + } + + PyDict_SetItem(dict, py_key, py_value); + } + + return dict; + + error: + Py_DECREF(dict); + return NULL; + +} + PyObject *svn_swig_py_locationhash_to_dict(apr_hash_t *hash) { @@ -550,6 +596,9 @@ DECLARE_SWIG_CONSTRUCTOR(lock, svn_lock_dup) DECLARE_SWIG_CONSTRUCTOR(auth_ssl_server_cert_info, svn_auth_ssl_server_cert_info_dup) +DECLARE_SWIG_CONSTRUCTOR(info, svn_info_dup) +DECLARE_SWIG_CONSTRUCTOR(commit_info, svn_commit_info_dup) +DECLARE_SWIG_CONSTRUCTOR(wc_notify, svn_wc_dup_notify) static PyObject *convert_log_changed_path(void *value, void *ctx, PyObject *py_pool) @@ -1506,6 +1555,41 @@ } +void svn_swig_py_notify_func2(void *baton, + const svn_wc_notify_t *notify, + apr_pool_t *pool) +{ + PyObject *function = baton; + PyObject *result; + svn_error_t *err = SVN_NO_ERROR; + + if (function == NULL || function == Py_None) + return; + + svn_swig_py_acquire_py_lock(); + + if ((result = PyObject_CallFunction(function, + (char *)"(O&O&)", + make_ob_wc_notify, notify, + make_ob_pool, pool)) == NULL) + { + err = callback_exception_error(); + } + else + { + /* The callback shouldn't be returning anything. */ + if (result != Py_None) + err = callback_bad_return_error("Not None"); + Py_DECREF(result); + } + + /* Our error has no place to go. :-( */ + if (err) + svn_error_clear(err); + + svn_swig_py_release_py_lock(); +} + void svn_swig_py_status_func(void *baton, const char *path, svn_wc_status_t *status) @@ -1814,6 +1898,38 @@ return err; } +svn_error_t *svn_swig_py_info_receiver_func(void *baton, + const char *path, + const svn_info_t *info, + apr_pool_t *pool) +{ + PyObject *receiver = baton; + PyObject *result; + svn_error_t *err = SVN_NO_ERROR; + + if ((receiver == NULL) || (receiver == Py_None)) + return SVN_NO_ERROR; + + svn_swig_py_acquire_py_lock(); + + if ((result = PyObject_CallFunction(receiver, + (char *)"sO&O&", + path, make_ob_info, info, + make_ob_pool, pool)) == NULL) + { + err = callback_exception_error(); + } + else + { + if (result != Py_None) + err = callback_bad_return_error("Not None"); + Py_DECREF(result); + } + + svn_swig_py_release_py_lock(); + + return err; +} svn_error_t *svn_swig_py_client_blame_receiver_func(void *baton, apr_int64_t line_no, @@ -2108,3 +2224,323 @@ *cred = creds; return err; } + +void +svn_swig_py_setup_ra_callbacks(svn_ra_callbacks2_t **callbacks, + void **baton, + PyObject *py_callbacks, + apr_pool_t *pool) +{ + svn_error_t *err = svn_ra_create_callbacks(callbacks, pool); + + if (err) + { + svn_swig_py_svn_exception(err); + return; + } + + *baton = py_callbacks; +} + +svn_error_t *svn_swig_py_commit_callback2(const svn_commit_info_t *commit_info, + void *baton, + apr_pool_t *pool) +{ + PyObject *receiver = baton; + PyObject *result; + svn_error_t *err = SVN_NO_ERROR; + + if ((receiver == NULL) || (receiver == Py_None)) + return SVN_NO_ERROR; + + svn_swig_py_acquire_py_lock(); + + if ((result = PyObject_CallFunction(receiver, + (char *)"O&O&", + make_ob_commit_info, commit_info, + make_ob_pool, pool)) == NULL) + { + err = callback_exception_error(); + } + else + { + if (result != Py_None) + err = callback_bad_return_error("Not None"); + Py_DECREF(result); + } + + svn_swig_py_release_py_lock(); + + return err; +} + +svn_error_t *svn_swig_py_commit_callback(svn_revnum_t new_revision, + const char *date, + const char *author, + void *baton) +{ + PyObject *receiver = baton; + PyObject *result; + svn_error_t *err = SVN_NO_ERROR; + + if ((receiver == NULL) || (receiver == Py_None)) + return SVN_NO_ERROR; + + svn_swig_py_acquire_py_lock(); + + if ((result = PyObject_CallFunction(receiver, + (char *)"lss", + new_revision, date, author)) == NULL) + { + err = callback_exception_error(); + } + else + { + if (result != Py_None) + err = callback_bad_return_error("Not None"); + Py_DECREF(result); + } + + svn_swig_py_release_py_lock(); + + return err; +} + +svn_error_t *svn_swig_py_ra_file_rev_handler_func( + void *baton, + const char *path, + svn_revnum_t rev, + apr_hash_t *rev_props, + svn_txdelta_window_handler_t *delta_handler, + void **delta_baton, + apr_array_header_t *prop_diffs, + apr_pool_t *pool) +{ + PyObject *handler = baton; + PyObject *result, *py_rev_props = NULL, *py_prop_diffs = NULL; + svn_error_t *err = SVN_NO_ERROR; + + if ((handler == NULL) || (handler == Py_None)) + return SVN_NO_ERROR; + + svn_swig_py_acquire_py_lock(); + + py_rev_props = svn_swig_py_prophash_to_dict(rev_props); + if (py_rev_props == NULL) + { + err = type_conversion_error("apr_hash_t *"); + goto error; + } + + py_prop_diffs = proparray_to_dict(prop_diffs); + + if (py_prop_diffs == NULL) + { + err = type_conversion_error("apr_array_header_t *"); + goto error; + } + + if ((result = PyObject_CallFunction(handler, + (char *)"slOOO&", + path, rev, py_rev_props, py_prop_diffs, + make_ob_pool, pool)) == NULL) + { + err = callback_exception_error(); + } + else + { + if (result != Py_None) + err = callback_bad_return_error("Not None"); + + /* FIXME: Support returned TxDeltaWindow object and + * set delta_handler and delta_baton */ + *delta_handler = NULL; + *delta_baton = NULL; + + Py_XDECREF(result); + } + +error: + + Py_XDECREF(py_rev_props); + Py_XDECREF(py_prop_diffs); + + svn_swig_py_release_py_lock(); + + return err; +} + +static svn_error_t *reporter_set_path(void *report_baton, + const char *path, + svn_revnum_t revision, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + PyObject *py_reporter = report_baton, *result; + + if (py_reporter == NULL || py_reporter == Py_None) + return SVN_NO_ERROR; + + svn_swig_py_acquire_py_lock(); + + if ((result = PyObject_CallMethod(py_reporter, + (char *)"set_path", + (char *)"slbsO&", + path, revision, + start_empty, lock_token, + make_ob_pool, pool)) == NULL) + { + err = callback_exception_error(); + } + else if (result != Py_None) + { + err = callback_bad_return_error("Not None"); + } + + Py_XDECREF(result); + + svn_swig_py_release_py_lock(); + + return err; +} + +static svn_error_t *reporter_delete_path(void *report_baton, + const char *path, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + PyObject *py_reporter = report_baton, *result; + + if (py_reporter == NULL || py_reporter == Py_None) + return SVN_NO_ERROR; + + svn_swig_py_acquire_py_lock(); + + if ((result = PyObject_CallMethod(py_reporter, + (char *)"delete_path", + (char *)"sO&", + path, + make_ob_pool, pool)) == NULL) + { + err = callback_exception_error(); + } + else if (result != Py_None) + { + err = callback_bad_return_error("Not None"); + } + + Py_XDECREF(result); + + svn_swig_py_release_py_lock(); + + return err; +} + +static svn_error_t *reporter_link_path(void *report_baton, + const char *path, + const char *url, + svn_revnum_t revision, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + PyObject *py_reporter = report_baton, *result; + + if (py_reporter == NULL || py_reporter == Py_None) + return SVN_NO_ERROR; + + svn_swig_py_acquire_py_lock(); + + if ((result = PyObject_CallMethod(py_reporter, + (char *)"link_path", + (char *)"sslbsO&", + path, url, revision, + start_empty, lock_token, + make_ob_pool, pool)) == NULL) + { + err = callback_exception_error(); + } + else if (result != Py_None) + { + err = callback_bad_return_error("Not None"); + } + + Py_XDECREF(result); + + svn_swig_py_release_py_lock(); + + return err; +} + +static svn_error_t *reporter_finish_report(void *report_baton, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + + PyObject *py_reporter = report_baton, *result; + + if (py_reporter == NULL || py_reporter == Py_None) + return SVN_NO_ERROR; + + svn_swig_py_acquire_py_lock(); + + if ((result = PyObject_CallMethod(py_reporter, + (char *)"finish_report", + (char *)"O&", + make_ob_pool, pool)) == NULL) + { + err = callback_exception_error(); + } + else if (result != Py_None) + { + err = callback_bad_return_error("Not None"); + } + + Py_XDECREF(result); + + svn_swig_py_release_py_lock(); + + return err; +} + +static svn_error_t *reporter_abort_report(void *report_baton, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + + PyObject *py_reporter = report_baton, *result; + + if (py_reporter == NULL || py_reporter == Py_None) + return SVN_NO_ERROR; + + svn_swig_py_acquire_py_lock(); + + if ((result = PyObject_CallMethod(py_reporter, + (char *)"abort_report", + (char *)"O&", + make_ob_pool, pool)) == NULL) + { + err = callback_exception_error(); + } + else if (result != Py_None) + { + err = callback_bad_return_error("Not None"); + } + + Py_XDECREF(result); + + svn_swig_py_release_py_lock(); + + return err; +} + +const svn_ra_reporter2_t swig_py_ra_reporter2 = { + reporter_set_path, + reporter_delete_path, + reporter_link_path, + reporter_finish_report, + reporter_abort_report +}; === modified file 'subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h' --- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h +++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h @@ -198,6 +198,11 @@ svn_wc_notify_state_t prop_state, svn_revnum_t revision); +SVN_SWIG_SWIGUTIL_EXPORT +void svn_swig_py_notify_func2(void *baton, + const svn_wc_notify_t *notify, + apr_pool_t *pool); + /* a status function that executes a Python function that is passed in via the baton argument */ SVN_SWIG_SWIGUTIL_EXPORT @@ -249,6 +254,13 @@ const char *msg, apr_pool_t *pool); +/* thunked info receiver function */ +SVN_SWIG_SWIGUTIL_EXPORT +svn_error_t *svn_swig_py_info_receiver_func(void *py_receiver, + const char *path, + const svn_info_t *info, + apr_pool_t *pool); + /* thunked blame receiver function */ SVN_SWIG_SWIGUTIL_EXPORT svn_error_t *svn_swig_py_client_blame_receiver_func(void *baton, @@ -303,6 +315,38 @@ svn_boolean_t may_save, apr_pool_t *pool); +SVN_SWIG_SWIGUTIL_EXPORT +void +svn_swig_py_setup_ra_callbacks(svn_ra_callbacks2_t **callbacks, + void **baton, + PyObject *py_callbacks, + apr_pool_t *pool); +SVN_SWIG_SWIGUTIL_EXPORT +svn_error_t *svn_swig_py_commit_callback2(const svn_commit_info_t *commit_info, + void *baton, + apr_pool_t *pool); + +SVN_SWIG_SWIGUTIL_EXPORT +svn_error_t *svn_swig_py_commit_callback(svn_revnum_t new_revision, + const char *date, + const char *author, + void *baton); + + +SVN_SWIG_SWIGUTIL_EXPORT +svn_error_t *svn_swig_py_ra_file_rev_handler_func( + void *baton, + const char *path, + svn_revnum_t rev, + apr_hash_t *rev_props, + svn_txdelta_window_handler_t *delta_handler, + void **delta_baton, + apr_array_header_t *prop_diffs, + apr_pool_t *pool); + +SVN_SWIG_SWIGUTIL_EXPORT +extern const svn_ra_reporter2_t swig_py_ra_reporter2; + #ifdef __cplusplus } #endif /* __cplusplus */ === modified file 'subversion/bindings/swig/python/tests/run_all.py' --- subversion/bindings/swig/python/tests/run_all.py +++ subversion/bindings/swig/python/tests/run_all.py @@ -1,6 +1,6 @@ import sys, os bindir = os.path.dirname(sys.argv[0]) -sys.path[0:0] = [ os.getcwd(), "%s/.libs" % os.getcwd(), \ +sys.path[0:0] = [ bindir, os.getcwd(), "%s/.libs" % os.getcwd(), \ "%s/.." % bindir, "%s/../.libs" % bindir ] # OSes without RPATH support are going to have to do things here to make @@ -17,6 +17,9 @@ import unittest import pool import repository +import client +import ra +import wc import trac.versioncontrol.tests # Run all tests @@ -24,7 +27,10 @@ def suite(): """Run all tests""" suite = unittest.TestSuite() + suite.addTest(client.suite()) suite.addTest(pool.suite()) + suite.addTest(ra.suite()) + suite.addTest(wc.suite()) suite.addTest(repository.suite()) suite.addTest(trac.versioncontrol.tests.suite()); return suite === modified file 'subversion/bindings/swig/svn_client.i' --- subversion/bindings/swig/svn_client.i +++ subversion/bindings/swig/svn_client.i @@ -25,6 +25,7 @@ #endif %include typemaps.i +%include constraints.i %include svn_global.swg %import core.i @@ -44,6 +45,11 @@ svn_client_ctx_t ** }; +%apply Pointer NONNULL { + const svn_opt_revision_t *revision, + const svn_opt_revision_t *peg_revision +}; + %apply const apr_array_header_t *STRINGLIST { const apr_array_header_t *targets, const apr_array_header_t *diff_options @@ -66,6 +72,16 @@ #ifdef SWIGPYTHON %apply svn_stream_t *WRAPPED_STREAM { svn_stream_t * }; + +/* members of svn_client_ctx_t */ +%apply void *PY_AS_VOID { + void *notify_baton, + void *log_msg_baton, + void *cancel_baton, + void *notify_baton2, + void *log_msg_baton2, + void *progress_baton +}; #endif /* ----------------------------------------------------------------------- @@ -200,6 +216,18 @@ } /* ----------------------------------------------------------------------- + Callback: svn_info_receiver_t + svn_client_info() +*/ + +%typemap(python, in) (svn_info_receiver_t receiver, + void *receiver_baton) { + $1 = svn_swig_py_info_receiver_func; + $2 = (void *)$input; +} + + +/* ----------------------------------------------------------------------- We use 'svn_wc_status_t *' in some custom code, but it isn't in the API anywhere. Thus, SWIG doesn't generate a typemap entry for it. by adding a simple declaration here, SWIG will insert a name for it. @@ -483,6 +511,15 @@ %include svn_time_h.swg %include svn_client_h.swg +#ifdef SWIGPYTHON + +/* provide Python with access to some thunks. */ +%constant svn_cancel_func_t svn_swig_py_cancel_func; +%constant svn_client_get_commit_log2_t svn_swig_py_get_commit_log_func; +%constant svn_wc_notify_func2_t svn_swig_py_notify_func; + +#endif + #ifdef SWIGRUBY %inline %{ static VALUE === modified file 'subversion/bindings/swig/svn_delta.i' --- subversion/bindings/swig/svn_delta.i +++ subversion/bindings/swig/svn_delta.i @@ -47,7 +47,8 @@ const char *error_info, const char *copyfrom_path, const char *copy_path, - const char *base_checksum + const char *base_checksum, + const char *text_checksum }; #ifdef SWIGPYTHON === modified file 'subversion/bindings/swig/svn_ra.i' --- subversion/bindings/swig/svn_ra.i +++ subversion/bindings/swig/svn_ra.i @@ -25,6 +25,7 @@ #endif %include typemaps.i +%include constraints.i %include svn_global.swg %import apr.swg @@ -39,6 +40,8 @@ %ignore svn_ra_local_init; %ignore svn_ra_dav_init; +%apply Pointer NONNULL { svn_ra_callbacks2_t *callbacks }; + /* ----------------------------------------------------------------------- %apply-ing of typemaps defined elsewhere */ @@ -48,13 +51,16 @@ const svn_ra_reporter2_t **reporter, void **report_baton, svn_dirent_t **dirent, - svn_lock_t **lock + svn_lock_t **lock, + const svn_delta_editor_t ** }; %apply apr_hash_t **PROPHASH { apr_hash_t **props }; +%apply apr_hash_t **DIRENTHASH { apr_hash_t **dirents }; %apply const char *MAY_BE_NULL { - const char *comment + const char *comment, + const char *lock_token }; %apply apr_hash_t *STRING_TO_STRING { @@ -112,6 +118,11 @@ svn_swig_rb_setup_ra_callbacks(&$1, &$2, $input, _global_pool); } +%typemap(python, in) (svn_ra_callbacks2_t *callbacks, + void *callback_baton) { + svn_swig_py_setup_ra_callbacks(&$1, &$2, $input, _global_pool); +} + %typemap(perl5, in) apr_hash_t *config { $1 = svn_swig_pl_objs_to_hash_by_name ($input, "svn_config_t *", _global_pool); @@ -134,6 +145,22 @@ $1 = svn_swig_rb_hash_to_apr_hash_revnum($input, _global_pool); } +#ifdef SWIGPYTHON +%typemap(in) (svn_ra_file_rev_handler_t handler, void *handler_baton) +{ + $1 = svn_swig_py_ra_file_rev_handler_func; + $2 = (void *)$input; +} +#endif + +#ifdef SWIGPYTHON +%typemap(in) (const svn_ra_reporter2_t *reporter, void *report_baton) +{ + $1 = (svn_ra_reporter2_t *)&swig_py_ra_reporter2; + $2 = (void *)$input; +} +#endif + /* ----------------------------------------------------------------------- */ %{ === modified file 'subversion/bindings/swig/svn_wc.i' --- subversion/bindings/swig/svn_wc.i +++ subversion/bindings/swig/svn_wc.i @@ -191,6 +191,20 @@ $1 = svn_swig_rb_array_to_apr_array_prop($input, pool); } +#ifdef SWIGPYTHON +%typemap(in, numinputs=0) + apr_array_header_t **patterns (apr_array_header_t *temp) +{ + $1 = &temp; +} +%typemap(argout, fragment="t_output_helper") + apr_array_header_t **patterns +{ + $result = t_output_helper($result, + svn_swig_py_array_to_list(*$1)); +} +#endif + /* ----------------------------------------------------------------------- Callback: svn_wc_notify_func_t svn_client_ctx_t @@ -237,6 +251,11 @@ $2 = $input; /* our function is the baton. */ } +%typemap(python,in) (svn_wc_notify_func2_t notify_func, void *notify_baton) { + $1 = svn_swig_py_notify_func2; + $2 = $input; /* our function is the baton. */ +} + %typemap(perl5,in) (svn_wc_status_func_t status_func, void *status_baton) { $1 = svn_swig_pl_status_func; $2 = $input; /* our function is the baton. */ === modified file 'subversion/libsvn_ra_svn/client.c' --- subversion/libsvn_ra_svn/client.c +++ subversion/libsvn_ra_svn/client.c @@ -1151,11 +1151,8 @@ } apr_pool_destroy(subpool); - if (nreceived <= limit) - { - /* Read the response. */ - SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); - } + /* Read the response. */ + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); return SVN_NO_ERROR; }