Attached is an update for debian/patches/jelmer-python-bindings that
includes the fix for the memory leak.
--
#! /bin/sh /usr/share/dpatch/dpatch-run
## jelmer-python-bindings by <[EMAIL PROTECTED]>
##
## DP: Updates to python bindings from Jelmer Vernooij. This is stuff
## DP: he needs for his svn<->bzr bridge, backported from trunk.
## DP:
## DP: Patch was pulled from Ubuntu edgy, then edited rather heavily:
## DP: (a) ported to 1.4.2, (b) removed lots of indentation fixing
## DP: noise, (c) reverted some duplicate adds.
@DPATCH@
Index: subversion/bindings/swig/include/apr.swg
--- a/subversion/bindings/swig/include/apr.swg
+++ b/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;
--- a/subversion/bindings/swig/include/proxy_apr.swg
+++ b/subversion/bindings/swig/include/proxy_apr.swg
@@ -159,6 +159,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"""
--- a/subversion/bindings/swig/include/svn_types.swg
+++ b/subversion/bindings/swig/include/svn_types.swg
@@ -444,6 +444,12 @@
svn_swig_rb_set_baton($result, (VALUE)$2);
};
+%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
*/
@@ -519,6 +525,29 @@
$1 = svn_swig_rb_make_stream($input);
}
+%typemap(python, in) (svn_commit_callback_t callback, void *callback_baton)
+{
+ $1 = svn_swig_py_commit_callback;
+ $2 = (void *)$input;
+}
+
+/* -----------------------------------------------------------------------
+ Mapper to automatically turn Python objects into void* batons on assignment
+*/
+
+%typemap(python, 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;
+ }
+}
+
/* -----------------------------------------------------------------------
Wrap the digest output for functions populating digests.
*/
--- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
+++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
@@ -104,6 +104,8 @@
static PyObject *_global_svn_swig_py_pool = NULL;
static char assertValid[] = "assert_valid";
static char parentPool[] = "_parent_pool";
+static char addOwnedRef[] = "_add_owned_ref";
+static char removeOwnedRef[] = "_remove_owned_ref";
static char wrap[] = "_wrap";
static char unwrap[] = "_unwrap";
static char setParentPool[] = "set_parent_pool";
@@ -166,6 +168,51 @@
return 0;
}
+
+/* Get the parent pool of a proxy object, or return the global application
+ * pool if one is not set. Returns a BORROWED reference! */
+static PyObject *proxy_get_pool(PyObject *proxy)
+{
+ PyObject *result;
+ if (PyObject_HasAttrString(proxy, parentPool))
+ {
+ result = PyObject_GetAttrString(proxy, parentPool);
+ Py_DECREF(result);
+ }
+ else
+ {
+ result = _global_svn_swig_py_pool;
+ }
+ return result;
+}
+
+/* Change an 'owned reference' allocated in a pool from oldRef to newRef.
+ * If oldRef is non-NULL and present in the parent pool of proxy, it is
removed.
+ */
+int svn_swig_py_pool_set_owned_ref(PyObject *proxy, PyObject *oldRef,
+ PyObject *newRef)
+{
+ PyObject *temp;
+ PyObject *py_pool = proxy_get_pool(proxy);
+
+ if (oldRef != NULL)
+ {
+ temp = PyObject_CallMethod(py_pool, removeOwnedRef, objectTuple, oldRef);
+ if (temp == NULL)
+ return 1;
+ else
+ Py_DECREF(temp);
+ }
+ if (newRef != NULL)
+ {
+ temp = PyObject_CallMethod(py_pool, addOwnedRef, objectTuple, newRef);
+ if (temp == NULL)
+ return 1;
+ else
+ Py_DECREF(temp);
+ }
+ return 0;
+}
/* Wrapper for SWIG_TypeQuery */
#define svn_swig_TypeQuery(x) SWIG_TypeQuery(x)
@@ -239,14 +286,9 @@
}
Py_DECREF(result);
}
- if (py_pool != NULL) {
- if (PyObject_HasAttrString(input, parentPool)) {
- *py_pool = PyObject_GetAttrString(input, parentPool);
- Py_DECREF(*py_pool);
- } else {
- *py_pool = _global_svn_swig_py_pool;
- }
- }
+ if (py_pool != NULL)
+ *py_pool = proxy_get_pool((PyObject *) input);
+
if (PyObject_HasAttrString(input, unwrap)) {
input = PyObject_CallMethod(input, unwrap, emptyTuple);
if (input == NULL) {
@@ -486,6 +528,52 @@
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)
{
@@ -780,8 +780,10 @@
PyObject *new_py_pool = svn_swig_NewPointerObj(new_pool, \
svn_swig_TypeQuery("apr_pool_t *"), application_py_pool, NULL); \
svn_##type##_t *new_value = dup(value, new_pool); \
- return svn_swig_NewPointerObjString(new_value, "svn_" #type "_t *", \
- new_py_pool); \
+ PyObject *obj = svn_swig_NewPointerObjString(new_value, "svn_" #type "_t *",
\
+ new_py_pool); \
+ Py_XDECREF(new_py_pool); \
+ return obj; \
}
DECLARE_SWIG_CONSTRUCTOR(txdelta_window, svn_txdelta_window_dup)
@@ -552,6 +642,8 @@
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)
@@ -1444,6 +1534,15 @@
return err;
}
+static svn_error_t *
+close_handler_pyio(void *baton)
+{
+ PyObject *py_io = baton;
+ Py_DECREF(py_io);
+ return SVN_NO_ERROR;
+}
+
+
svn_stream_t *
svn_swig_py_make_stream(PyObject *py_io, apr_pool_t *pool)
{
@@ -1454,9 +1553,11 @@
* bindings, and we will be finished with the py_io object before we return
* to python. I.e. DO NOT STORE AWAY THE RESULTING svn_stream_t * for use
* over multiple calls into the bindings. */
+ Py_INCREF(py_io);
stream = svn_stream_create(py_io, pool);
svn_stream_set_read(stream, read_handler_pyio);
svn_stream_set_write(stream, write_handler_pyio);
+ svn_stream_set_close(stream, close_handler_pyio);
return stream;
}
@@ -1504,6 +1605,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)
@@ -2138,3 +2274,377 @@
*cred = creds;
return err;
}
+
+/* svn_ra_callbacks_t */
+static svn_error_t *
+ra_callbacks_open_tmp_file(apr_file_t **fp,
+ void *callback_baton,
+ apr_pool_t *pool)
+{
+ PyObject *callbacks = (PyObject *)callback_baton;
+ PyObject *result;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ svn_swig_py_acquire_py_lock();
+
+ if ((result = PyObject_CallMethod(callbacks,
+ "open_tmp_file",
+ (char *)"O&",
+ make_ob_pool, pool)) == NULL)
+ {
+ err = callback_exception_error();
+ }
+ else if (result == Py_None)
+ {
+ *fp = NULL;
+ }
+ else
+ {
+ *fp = svn_swig_py_make_file(result, pool);
+ if (*fp == NULL)
+ {
+ err = callback_exception_error();
+ }
+ }
+
+ Py_XDECREF(result);
+ svn_swig_py_release_py_lock();
+ 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);
+ PyObject *py_auth_baton;
+
+ if (err)
+ {
+ svn_swig_py_svn_exception(err);
+ return;
+ }
+
+ (*callbacks)->open_tmp_file = ra_callbacks_open_tmp_file;
+
+ py_auth_baton = PyObject_GetAttrString(py_callbacks, "auth_baton");
+
+ if (svn_swig_ConvertPtrString(py_auth_baton,
+ (void **)&((*callbacks)->auth_baton),
+ "svn_auth_baton_t *"))
+ {
+ err = type_conversion_error("svn_auth_baton_t *");
+ svn_swig_py_svn_exception(err);
+ Py_DECREF(py_auth_baton);
+ return;
+ }
+
+ Py_XDECREF(py_auth_baton);
+
+ *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
+};
--- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
+++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
@@ -79,6 +79,9 @@
SVN_SWIG_SWIGUTIL_EXPORT
void svn_swig_get_application_pool(PyObject **py_pool, apr_pool_t **pool);
+/* Set a Python 'owned' reference on the pool of the given proxy object */
+int svn_swig_py_pool_set_owned_ref(PyObject *proxy, PyObject *oldRef, PyObject
*newRef);
+
/*** SWIG Wrappers ***/
@@ -198,6 +201,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
@@ -256,6 +264,13 @@
const svn_info_t *info,
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,
@@ -310,6 +325,45 @@
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;
+
+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);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
--- a/subversion/bindings/swig/python/tests/client.py
+++ b/subversion/bindings/swig/python/tests/client.py
@@ -1,9 +1,9 @@
-import unittest, os
+import unittest, os, tempfile, types
-from svn import core, repos, fs, delta, client
+from svn import core, repos, fs, delta, client, wc
from trac.versioncontrol.tests.svn_fs import SubversionRepositoryTestSetup, \
- REPOS_PATH, REPOS_URL
+ REPOS_PATH
from urllib import pathname2url
class SubversionRepositoryTestCase(unittest.TestCase):
@@ -19,6 +19,7 @@
]
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
@@ -26,22 +27,80 @@
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
- client.info(REPOS_URL, revt, revt, self.info_receiver,
+ 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)
+ 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',
--- a/subversion/bindings/swig/python/tests/ra.py
+++ b/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.assert_(dirents.has_key('trunk'))
+ self.assert_(dirents.has_key('branches'))
+ self.assert_(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.assert_(props.has_key(core.SVN_PROP_ENTRY_UUID))
+ self.assert_(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.assert_(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())
--- a/subversion/bindings/swig/python/tests/run_all.py
+++ b/subversion/bindings/swig/python/tests/run_all.py
@@ -18,6 +18,8 @@
import pool
import repository
import client
+import ra
+import wc
import trac.versioncontrol.tests
# Run all tests
@@ -27,6 +29,8 @@
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
--- a/subversion/bindings/swig/python/tests/wc.py
+++ b/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.assert_(wc.is_adm_dir(".svn"))
+ self.assert_(not wc.is_adm_dir(".foosvn"))
+
+ def test_get_adm_dir(self):
+ self.assert_(isinstance(wc.get_adm_dir(), basestring))
+
+ def test_set_adm_dir(self):
+ self.assertRaises(SubversionException, wc.set_adm_dir, ".foobar")
+ self.assert_(wc.is_adm_dir(".svn"))
+ self.assert_(not wc.is_adm_dir("_svn"))
+ self.assert_(not wc.is_adm_dir(".foobar"))
+ wc.set_adm_dir("_svn")
+ self.assert_(wc.is_adm_dir("_svn"))
+ self.assertEqual("_svn", wc.get_adm_dir())
+ wc.set_adm_dir(".svn")
+ self.assert_(not 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.assert_(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())
--- a/subversion/bindings/swig/svn_client.i
+++ b/subversion/bindings/swig/svn_client.i
@@ -25,6 +25,7 @@
#endif
%include typemaps.i
+%include constraints.i
%include svn_global.swg
%import core.i
@@ -49,6 +50,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
@@ -71,6 +77,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
/* -----------------------------------------------------------------------
@@ -240,6 +256,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.
@@ -498,6 +526,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
--- a/subversion/bindings/swig/svn_delta.i
+++ b/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
@@ -148,6 +149,17 @@
#endif
%}
+#ifdef SWIGPYTHON
+%inline %{
+svn_error_t *svn_delta_invoke_txdelta_window_handler (
+ svn_txdelta_window_handler_t handler,
+ svn_txdelta_window_t *window, void *baton) {
+ return handler(window, baton);
+}
+%}
+#endif
+
+
/* -----------------------------------------------------------------------
handle svn_txdelta_window_t::ops
*/
--- a/subversion/bindings/swig/svn_ra.i
+++ b/subversion/bindings/swig/svn_ra.i
@@ -25,6 +25,7 @@
#endif
%include typemaps.i
+%include constraints.i
%include svn_global.swg
%import apr.swg
@@ -49,10 +50,12 @@
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,
@@ -120,6 +123,11 @@
svn_swig_rb_setup_ra_callbacks(&$1, &$2, $input, _global_pool);
}
+%typemap(python, in) (const 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);
@@ -142,6 +150,18 @@
$1 = svn_swig_rb_hash_to_apr_hash_revnum($input, _global_pool);
}
+%typemap(python, in) (svn_ra_file_rev_handler_t handler, void *handler_baton)
+{
+ $1 = svn_swig_py_ra_file_rev_handler_func;
+ $2 = (void *)$input;
+}
+
+%typemap(python, in) (const svn_ra_reporter2_t *reporter, void *report_baton)
+{
+ $1 = (svn_ra_reporter2_t *)&swig_py_ra_reporter2;
+ $2 = (void *)$input;
+}
+
/* ----------------------------------------------------------------------- */
%{
--- a/subversion/bindings/swig/svn_wc.i
+++ b/subversion/bindings/swig/svn_wc.i
@@ -226,6 +226,18 @@
}
}
+%typemap(python, in, numinputs=0)
+ apr_array_header_t **patterns (apr_array_header_t *temp)
+{
+ $1 = &temp;
+}
+%typemap(python, argout, fragment="t_output_helper")
+ apr_array_header_t **patterns
+{
+ $result = t_output_helper($result,
+ svn_swig_py_array_to_list(*$1));
+}
+
/* -----------------------------------------------------------------------
Callback: svn_wc_notify_func_t
svn_client_ctx_t
@@ -272,6 +284,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. */