Modified: subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c (original) +++ subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c Mon Nov 4 05:59:36 2019 @@ -26,6 +26,7 @@ #include <Python.h> + #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> @@ -50,6 +51,19 @@ #include "swig_python_external_runtime.swg" #include "swigutil_py.h" +#include "swigutil_py3c.h" + +#if IS_PY3 + +/* In Python 3 use the bytes format character for raw data */ +#define SVN_SWIG_BYTES_FMT "y" + +#else + +/* In Python 2 use the string format character for raw data */ +#define SVN_SWIG_BYTES_FMT "s" + +#endif /* Py_ssize_t for old Pythons */ /* This code is as recommended by: */ @@ -142,6 +156,35 @@ apr_status_t svn_swig_py_initialize(void return APR_SUCCESS; } +FILE *svn_swig_py_as_file(PyObject *pyfile) +{ +#if IS_PY3 + FILE *fp = NULL; + int fd = PyObject_AsFileDescriptor(pyfile); + if (fd >= 0) + { + PyObject *mode_obj; + PyObject *mode_byte_obj = NULL; + char *mode = NULL; + + /* If any Python API returns NULL, then the Python exception is set and + this function will return NULL signifying to the caller that an error + occurred. */ + if ( NULL != (mode_obj = PyObject_GetAttrString(pyfile, "mode")) + && NULL != (mode_byte_obj = PyUnicode_AsUTF8String(mode_obj)) + && NULL != (mode = PyBytes_AsString(mode_byte_obj))) + fp = fdopen(fd, mode); + + Py_XDECREF(mode_obj); + Py_XDECREF(mode_byte_obj); + } + + return fp; +#else + return PyFile_AsFile(pyfile); +#endif +} + int svn_swig_py_get_pool_arg(PyObject *args, swig_type_info *type, PyObject **py_pool, apr_pool_t **pool) { @@ -150,14 +193,25 @@ int svn_swig_py_get_pool_arg(PyObject *a if (argnum >= 0) { PyObject *input = PyTuple_GET_ITEM(args, argnum); - if (input != Py_None && PyObject_HasAttrString(input, markValid)) + if (input != Py_None) { - *pool = svn_swig_py_must_get_ptr(input, type, argnum+1); - if (*pool == NULL) - return 1; - *py_pool = input; - Py_INCREF(input); - return 0; + PyObject *fn; + if (NULL != (fn = PyObject_GetAttrString(input, markValid))) + { + Py_DECREF(fn); + + *pool = svn_swig_py_must_get_ptr(input, type, argnum+1); + if (*pool == NULL) + return 1; + *py_pool = input; + Py_INCREF(input); + return 0; + } + else + { + /* Clear any getattr() error, it isn't needed. */ + PyErr_Clear(); + } } } @@ -221,13 +275,20 @@ static int proxy_set_pool(PyObject **pro { if (pool == NULL) { - if (PyObject_HasAttrString(*proxy, setParentPool)) + PyObject *setFn; + if (NULL != (setFn = PyObject_GetAttrString(*proxy, setParentPool))) { - result = PyObject_CallMethod(*proxy, setParentPool, emptyTuple); + result = PyObject_CallObject(setFn, NULL); + Py_DECREF(setFn); if (result == NULL) return 1; Py_DECREF(result); } + else + { + /* Clear any getattr() error, it isn't needed. */ + PyErr_Clear(); + } } else { @@ -285,23 +346,45 @@ static PyObject *svn_swig_NewPointerObjS return svn_swig_py_new_pointer_obj(ptr, typeinfo, py_pool, NULL); } -/** Wrapper for SWIG_ConvertPtr */ -int svn_swig_py_convert_ptr(PyObject *input, void **obj, swig_type_info *type) +static int svn_swig_ensure_valid_swig_wrapper(PyObject *input) { - if (PyObject_HasAttrString(input, assertValid)) + PyObject *assertFn; + PyObject *unwrapFn; + if (NULL != (assertFn = PyObject_GetAttrString(input, assertValid))) { - PyObject *result = PyObject_CallMethod(input, assertValid, emptyTuple); + PyObject *result = PyObject_CallObject(assertFn, NULL); + Py_DECREF(assertFn); if (result == NULL) return 1; Py_DECREF(result); } - if (PyObject_HasAttrString(input, unwrap)) + else { - input = PyObject_CallMethod(input, unwrap, emptyTuple); + /* Clear any getattr() error, it isn't needed. */ + PyErr_Clear(); + } + if (NULL != (unwrapFn = PyObject_GetAttrString(input, unwrap))) + { + input = PyObject_CallObject(unwrapFn, NULL); + Py_DECREF(unwrapFn); if (input == NULL) return 1; Py_DECREF(input); } + else + { + /* Clear any getattr() error, it isn't needed. */ + PyErr_Clear(); + } + + return 0; +} + +/** Wrapper for SWIG_ConvertPtr */ +int svn_swig_py_convert_ptr(PyObject *input, void **obj, swig_type_info *type) +{ + if (svn_swig_ensure_valid_swig_wrapper(input)) + return 1; return SWIG_ConvertPtr(input, obj, type, SWIG_POINTER_EXCEPTION | 0); } @@ -316,21 +399,8 @@ static int svn_swig_ConvertPtrString(PyO /** Wrapper for SWIG_MustGetPtr */ void *svn_swig_py_must_get_ptr(void *input, swig_type_info *type, int argnum) { - if (PyObject_HasAttrString(input, assertValid)) - { - PyObject *result = PyObject_CallMethod(input, assertValid, emptyTuple); - if (result == NULL) - return NULL; - Py_DECREF(result); - } - - if (PyObject_HasAttrString(input, unwrap)) - { - input = PyObject_CallMethod(input, unwrap, emptyTuple); - if (input == NULL) - return NULL; - Py_DECREF((PyObject *) input); - } + if (svn_swig_ensure_valid_swig_wrapper(input)) + return NULL; return SWIG_MustGetPtr(input, type, argnum, SWIG_POINTER_EXCEPTION | 0); } @@ -370,14 +440,14 @@ void svn_swig_py_svn_exception(svn_error Py_INCREF(Py_None); message_ob = Py_None; } - else if ((message_ob = PyString_FromString(err->message)) == NULL) + else if ((message_ob = PyStr_FromString(err->message)) == NULL) goto finished; if (err->file == NULL) { Py_INCREF(Py_None); file_ob = Py_None; } - else if ((file_ob = PyString_FromString(err->file)) == NULL) + else if ((file_ob = PyStr_FromString(err->file)) == NULL) goto finished; if ((line_ob = PyInt_FromLong(err->line)) == NULL) goto finished; @@ -436,6 +506,36 @@ void svn_swig_py_svn_exception(svn_error /*** Helper/Conversion Routines ***/ +/* Function to get char * representation of bytes/str object. This is + the replacement of typemap(in, parse="s") and typemap(in, parse="z") + to accept both of bytes object and str object, and it assumes to be + used from those typemaps only. + Note: type of return value should be char const *, however, as SWIG + produces variables for C function without 'const' modifier, to avoid + ton of cast in SWIG produced C code we drop it from return value + types as well */ +char *svn_swig_py_string_to_cstring(PyObject *input, int maybe_null, + const char * funcsym, const char * argsym) +{ + char *retval = NULL; + if (PyBytes_Check(input)) + { + retval = PyBytes_AsString(input); + } + else if (PyUnicode_Check(input)) + { + retval = (char *)PyStr_AsUTF8(input); + } + else if (input != Py_None || ! maybe_null) + { + PyErr_Format(PyExc_TypeError, + "%s() argument %s must be bytes or str%s, not %s", + funcsym, argsym, maybe_null?" or None":"", + Py_TYPE(input)->tp_name); + } + return retval; +} + /* Functions for making Python wrappers around Subversion structs */ static PyObject *make_ob_pool(void *pool) { @@ -470,32 +570,89 @@ static PyObject *make_ob_error(svn_error /***/ +static void svn_swig_py_string_type_exception(int maybe_null) +{ + PyErr_Format(PyExc_TypeError, "not a bytes or a str%s", + maybe_null?" or None":""); +} + /* Conversion from Python single objects (not hashes/lists/etc.) to Subversion types. */ static char *make_string_from_ob(PyObject *ob, apr_pool_t *pool) { + /* caller should not expect to raise TypeError: check return value + whether it is NULL or not, if needed */ + if (PyBytes_Check(ob)) + { + return apr_pstrdup(pool, PyBytes_AsString(ob)); + } + if (PyUnicode_Check(ob)) + { + /* PyStr_AsUTF8() may cause UnicodeEncodeError, + but apr_pstrdup() allows NULL for s */ + return apr_pstrdup(pool, PyStr_AsUTF8(ob)); + } + return NULL; +} + +static char *make_string_from_ob_maybe_null(PyObject *ob, apr_pool_t *pool) +{ + char * retval; if (ob == Py_None) - return NULL; - if (! PyString_Check(ob)) { - PyErr_SetString(PyExc_TypeError, "not a string"); return NULL; } - return apr_pstrdup(pool, PyString_AS_STRING(ob)); + retval = make_string_from_ob(ob, pool); + if (!retval) + { + if (!PyErr_Occurred()) + { + svn_swig_py_string_type_exception(TRUE); + } + } + return retval; } + static svn_string_t *make_svn_string_from_ob(PyObject *ob, apr_pool_t *pool) { + /* caller should not expect to raise TypeError: check return value + whether it is NULL or not, if needed */ + if (PyBytes_Check(ob)) + { + return svn_string_create(PyBytes_AsString(ob), pool); + } + if (PyUnicode_Check(ob)) + { + /* PyStr_AsUTF8() may cause UnicodeEncodeError, + and svn_string_create() does not allows NULL for cstring */ + const char *obstr = PyStr_AsUTF8(ob); + if (obstr) + { + return svn_string_create(obstr, pool); + } + } + return NULL; +} + +static svn_string_t *make_svn_string_from_ob_maybe_null(PyObject *ob, + apr_pool_t *pool) +{ + svn_string_t * retval; if (ob == Py_None) - return NULL; - if (! PyString_Check(ob)) { - PyErr_SetString(PyExc_TypeError, "not a string"); return NULL; } - return svn_string_create(PyString_AS_STRING(ob), pool); + retval = make_svn_string_from_ob(ob, pool); + if (!retval) + { + if (!PyErr_Occurred()) + { + svn_swig_py_string_type_exception(TRUE); + } + } + return retval; } - /***/ static PyObject *convert_hash(apr_hash_t *hash, @@ -527,7 +684,7 @@ static PyObject *convert_hash(apr_hash_t return NULL; } /* ### gotta cast this thing cuz Python doesn't use "const" */ - if (PyDict_SetItemString(dict, (char *)key, value) == -1) + if (PyDict_SetItem(dict, PyBytes_FromString((char *)key), value) == -1) { Py_DECREF(value); Py_DECREF(dict); @@ -552,11 +709,10 @@ static PyObject *convert_svn_string_t(vo const svn_string_t *s = value; - /* ### gotta cast this thing cuz Python doesn't use "const" */ - return PyString_FromStringAndSize((void *)s->data, s->len); + return PyBytes_FromStringAndSize(s->data, s->len); } -/* Convert a C string into a Python String object (or a reference to +/* Convert a C string into a Python Bytes object (or a reference to Py_None if CSTRING is NULL). */ static PyObject *cstring_to_pystring(const char *cstring) { @@ -566,7 +722,7 @@ static PyObject *cstring_to_pystring(con Py_INCREF(Py_None); return retval; } - return PyString_FromString(cstring); + return PyBytes_FromString(cstring); } static PyObject *convert_svn_client_commit_item3_t(void *value, void *ctx) @@ -642,8 +798,7 @@ PyObject *svn_swig_py_prophash_to_dict(a static PyObject *convert_string(void *value, void *ctx, PyObject *py_pool) { - /* ### gotta cast this thing cuz Python doesn't use "const" */ - return PyString_FromString((const char *)value); + return PyBytes_FromString((const char *)value); } PyObject *svn_swig_py_stringhash_to_dict(apr_hash_t *hash) @@ -725,7 +880,7 @@ svn_swig_py_propinheriteditemarray_to_di apr_hash_t *prop_hash = prop_inherited_item->prop_hash; PyObject *py_key, *py_value; - py_key = PyString_FromString(prop_inherited_item->path_or_url); + py_key = PyBytes_FromString(prop_inherited_item->path_or_url); if (py_key == NULL) goto error; @@ -769,7 +924,7 @@ PyObject *svn_swig_py_proparray_to_dict( prop = APR_ARRAY_IDX(array, i, svn_prop_t); - py_key = PyString_FromString(prop.name); + py_key = PyBytes_FromString(prop.name); if (py_key == NULL) goto error; @@ -780,8 +935,8 @@ PyObject *svn_swig_py_proparray_to_dict( } else { - py_value = PyString_FromStringAndSize((void *)prop.value->data, - prop.value->len); + py_value = PyBytes_FromStringAndSize(prop.value->data, + prop.value->len); if (py_value == NULL) { Py_DECREF(py_key); @@ -831,7 +986,7 @@ PyObject *svn_swig_py_locationhash_to_di Py_DECREF(dict); return NULL; } - value = PyString_FromString((char *)v); + value = PyBytes_FromString((const char *)v); if (value == NULL) { Py_DECREF(key); @@ -881,6 +1036,7 @@ DECLARE_SWIG_CONSTRUCTOR(info, svn_info_ DECLARE_SWIG_CONSTRUCTOR(location_segment, svn_location_segment_dup) DECLARE_SWIG_CONSTRUCTOR(commit_info, svn_commit_info_dup) DECLARE_SWIG_CONSTRUCTOR(wc_notify, svn_wc_dup_notify) +DECLARE_SWIG_CONSTRUCTOR(client_status, svn_client_status_dup) static PyObject *convert_log_changed_path(void *value, void *ctx, PyObject *py_pool) @@ -895,7 +1051,7 @@ PyObject *svn_swig_py_c_strings_to_list( while ((s = *strings++) != NULL) { - PyObject *ob = PyString_FromString(s); + PyObject *ob = PyBytes_FromString(s); if (ob == NULL) goto error; @@ -938,7 +1094,7 @@ PyObject *svn_swig_py_changed_path_hash_ Py_DECREF(dict); return NULL; } - if (PyDict_SetItemString(dict, (char *)key, value) == -1) + if (PyDict_SetItem(dict, PyBytes_FromString((char *)key), value) == -1) { Py_DECREF(value); Py_DECREF(dict); @@ -974,7 +1130,7 @@ PyObject *svn_swig_py_changed_path2_hash Py_DECREF(dict); return NULL; } - if (PyDict_SetItemString(dict, (char *)key, value) == -1) + if (PyDict_SetItem(dict, PyBytes_FromString((char *)key), value) == -1) { Py_DECREF(value); Py_DECREF(dict); @@ -986,6 +1142,9 @@ PyObject *svn_swig_py_changed_path2_hash return dict; } +#define TYPE_ERROR_DICT_STRING_KEY \ + "dictionary keys aren't bytes or str objects" + apr_hash_t *svn_swig_py_stringhash_from_dict(PyObject *dict, apr_pool_t *pool) { @@ -1010,11 +1169,18 @@ apr_hash_t *svn_swig_py_stringhash_from_ PyObject *key = PyList_GetItem(keys, i); PyObject *value = PyDict_GetItem(dict, key); const char *propname = make_string_from_ob(key, pool); - const char *propval = make_string_from_ob(value, pool); - if (! (propname && propval)) + if (!propname) + { + if (!PyErr_Occurred()) + { + PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY); + } + Py_DECREF(keys); + return NULL; + } + const char *propval = make_string_from_ob_maybe_null(value, pool); + if (PyErr_Occurred()) { - PyErr_SetString(PyExc_TypeError, - "dictionary keys/values aren't strings"); Py_DECREF(keys); return NULL; } @@ -1047,6 +1213,15 @@ apr_hash_t *svn_swig_py_mergeinfo_from_d PyObject *key = PyList_GetItem(keys, i); PyObject *value = PyDict_GetItem(dict, key); const char *pathname = make_string_from_ob(key, pool); + if (!pathname) + { + if (!PyErr_Occurred()) + { + PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY); + } + Py_DECREF(keys); + return NULL; + } const svn_rangelist_t *ranges = svn_swig_py_seq_to_array(value, sizeof(const svn_merge_range_t *), svn_swig_py_unwrap_struct_ptr, @@ -1054,10 +1229,10 @@ apr_hash_t *svn_swig_py_mergeinfo_from_d pool ); - if (! (pathname && ranges)) + if (!ranges) { PyErr_SetString(PyExc_TypeError, - "dictionary keys aren't strings or values aren't svn_merge_range_t *'s"); + "dictionary values aren't svn_merge_range_t *'s"); Py_DECREF(keys); return NULL; } @@ -1092,11 +1267,18 @@ apr_array_header_t *svn_swig_py_proparra PyObject *value = PyDict_GetItem(dict, key); svn_prop_t *prop = apr_palloc(pool, sizeof(*prop)); prop->name = make_string_from_ob(key, pool); - prop->value = make_svn_string_from_ob(value, pool); - if (! (prop->name && prop->value)) + if (! prop->name) + { + if (!PyErr_Occurred()) + { + PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY); + } + Py_DECREF(keys); + return NULL; + } + prop->value = make_svn_string_from_ob_maybe_null(value, pool); + if (PyErr_Occurred()) { - PyErr_SetString(PyExc_TypeError, - "dictionary keys/values aren't strings"); Py_DECREF(keys); return NULL; } @@ -1130,11 +1312,18 @@ apr_hash_t *svn_swig_py_prophash_from_di PyObject *key = PyList_GetItem(keys, i); PyObject *value = PyDict_GetItem(dict, key); const char *propname = make_string_from_ob(key, pool); - svn_string_t *propval = make_svn_string_from_ob(value, pool); - if (! (propname && propval)) + if (!propname) + { + if (!PyErr_Occurred()) + { + PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY); + } + Py_DECREF(keys); + return NULL; + } + svn_string_t *propval = make_svn_string_from_ob_maybe_null(value, pool); + if (PyErr_Occurred()) { - PyErr_SetString(PyExc_TypeError, - "dictionary keys/values aren't strings"); Py_DECREF(keys); return NULL; } @@ -1172,8 +1361,10 @@ apr_hash_t *svn_swig_py_path_revs_hash_f if (!(path)) { - PyErr_SetString(PyExc_TypeError, - "dictionary keys aren't strings"); + if (!PyErr_Occurred()) + { + PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY); + } Py_DECREF(keys); return NULL; } @@ -1227,8 +1418,10 @@ apr_hash_t *svn_swig_py_struct_ptr_hash_ if (!c_key) { - PyErr_SetString(PyExc_TypeError, - "dictionary keys aren't strings"); + if (!PyErr_Occurred()) + { + PyErr_SetString(PyExc_TypeError, TYPE_ERROR_DICT_STRING_KEY); + } Py_DECREF(keys); return NULL; } @@ -1252,8 +1445,21 @@ svn_swig_py_unwrap_string(PyObject *sour void *baton) { const char **ptr_dest = destination; - *ptr_dest = PyString_AsString(source); - + if (PyBytes_Check(source)) + { + *ptr_dest = PyBytes_AsString(source); + } + else if (PyUnicode_Check(source)) + { + *ptr_dest = PyStr_AsUTF8(source); + } + else + { + PyErr_Format(PyExc_TypeError, + "Expected bytes or str object, %s found", + Py_TYPE(source)->tp_name); + *ptr_dest = NULL; + } if (*ptr_dest != NULL) return 0; else @@ -1371,7 +1577,7 @@ PyObject *svn_swig_py_array_to_list(cons for (i = 0; i < array->nelts; ++i) { PyObject *ob = - PyString_FromString(APR_ARRAY_IDX(array, i, const char *)); + PyBytes_FromString(APR_ARRAY_IDX(array, i, const char *)); if (ob == NULL) goto error; PyList_SET_ITEM(list, i, ob); @@ -1425,7 +1631,7 @@ commit_item_array_to_list(const apr_arra } - + /*** Errors ***/ /* Convert a given SubversionException to an svn_error_t. On failure returns @@ -1446,13 +1652,13 @@ static svn_error_t *exception_to_error(P if ((message_ob = PyObject_GetAttrString(exc, "message")) == NULL) goto finished; - message = PyString_AsString(message_ob); + message = PyStr_AsString(message_ob); if (PyErr_Occurred()) goto finished; if ((file_ob = PyObject_GetAttrString(exc, "file")) == NULL) goto finished; if (file_ob != Py_None) - file = PyString_AsString(file_ob); + file = PyStr_AsString(file_ob); if (PyErr_Occurred()) goto finished; if ((line_ob = PyObject_GetAttrString(exc, "line")) == NULL) @@ -1679,7 +1885,8 @@ static svn_error_t *delete_entry(const c /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"delete_entry", - (char *)"slOO&", path, revision, ib->baton, + (char *)SVN_SWIG_BYTES_FMT "lOO&", + path, revision, ib->baton, make_ob_pool, pool)) == NULL) { err = callback_exception_error(); @@ -1710,7 +1917,12 @@ static svn_error_t *add_directory(const /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"add_directory", - (char *)"sOslO&", path, ib->baton, +#if IS_PY3 + (char *)"yOylO&", +#else + (char *)"sOslO&", +#endif + path, ib->baton, copyfrom_path, copyfrom_revision, make_ob_pool, dir_pool)) == NULL) { @@ -1741,8 +1953,8 @@ static svn_error_t *open_directory(const /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"open_directory", - (char *)"sOlO&", path, ib->baton, - base_revision, + (char *)SVN_SWIG_BYTES_FMT "OlO&", + path, ib->baton, base_revision, make_ob_pool, dir_pool)) == NULL) { err = callback_exception_error(); @@ -1771,7 +1983,12 @@ static svn_error_t *change_dir_prop(void /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"change_dir_prop", - (char *)"Oss#O&", ib->baton, name, +#if IS_PY3 + (char *)"Oyy#O&", +#else + (char *)"Oss#O&", +#endif + ib->baton, name, value ? value->data : NULL, value ? value->len : 0, make_ob_pool, pool)) == NULL) @@ -1810,7 +2027,12 @@ static svn_error_t *add_file(const char /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"add_file", - (char *)"sOslO&", path, ib->baton, +#if IS_PY3 + (char *)"yOylO&", +#else + (char *)"sOslO&", +#endif + path, ib->baton, copyfrom_path, copyfrom_revision, make_ob_pool, file_pool)) == NULL) { @@ -1842,8 +2064,8 @@ static svn_error_t *open_file(const char /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"open_file", - (char *)"sOlO&", path, ib->baton, - base_revision, + (char *)SVN_SWIG_BYTES_FMT "OlO&", + path, ib->baton, base_revision, make_ob_pool, file_pool)) == NULL) { err = callback_exception_error(); @@ -1916,7 +2138,12 @@ static svn_error_t *apply_textdelta(void /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"apply_textdelta", - (char *)"(Os)", ib->baton, +#if IS_PY3 + (char *)"(Oy)", +#else + (char *)"(Os)", +#endif + ib->baton, base_checksum)) == NULL) { err = callback_exception_error(); @@ -1961,7 +2188,12 @@ static svn_error_t *change_file_prop(voi /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"change_file_prop", - (char *)"Oss#O&", ib->baton, name, +#if IS_PY3 + (char *)"Oyy#O&", +#else + (char *)"Oss#O&", +#endif + ib->baton, name, value ? value->data : NULL, value ? value->len : 0, make_ob_pool, pool)) == NULL) @@ -1991,7 +2223,12 @@ static svn_error_t *close_file(void *fil /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"close_file", - (char *)"(Os)", ib->baton, +#if IS_PY3 + (char *)"(Oy)", +#else + (char *)"(Os)", +#endif + ib->baton, text_checksum)) == NULL) { err = callback_exception_error(); @@ -2099,7 +2336,7 @@ static svn_error_t *parse_fn3_uuid_recor /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"uuid_record", - (char *)"sO&", uuid, + (char *)SVN_SWIG_BYTES_FMT "O&", uuid, make_ob_pool, pool)) == NULL) { err = callback_exception_error(); @@ -2188,7 +2425,12 @@ static svn_error_t *parse_fn3_set_revisi /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"set_revision_property", - (char *)"Oss#", ib->baton, name, +#if IS_PY3 + (char *)"Oyy#", +#else + (char *)"Oss#", +#endif + ib->baton, name, value ? value->data : NULL, value ? value->len : 0)) == NULL) { @@ -2218,7 +2460,12 @@ static svn_error_t *parse_fn3_set_node_p /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"set_node_property", - (char *)"Oss#", ib->baton, name, +#if IS_PY3 + (char *)"Oyy#", +#else + (char *)"Oss#", +#endif + ib->baton, name, value ? value->data : NULL, value ? value->len : 0)) == NULL) { @@ -2247,7 +2494,8 @@ static svn_error_t *parse_fn3_delete_nod /* ### python doesn't have 'const' on the method name and format */ if ((result = PyObject_CallMethod(ib->editor, (char *)"delete_node_property", - (char *)"Os", ib->baton, name)) == NULL) + (char *)"O" SVN_SWIG_BYTES_FMT, + ib->baton, name)) == NULL) { err = callback_exception_error(); goto finished; @@ -2435,14 +2683,23 @@ apr_file_t *svn_swig_py_make_file(PyObje { apr_file_t *apr_file = NULL; apr_status_t apr_err; + const char* fname = NULL; if (py_file == NULL || py_file == Py_None) return NULL; - if (PyString_Check(py_file)) + /* check if input is a path */ + if (PyBytes_Check(py_file)) + { + fname = PyBytes_AsString(py_file); + } + else if (PyUnicode_Check(py_file)) + { + fname = PyStr_AsUTF8(py_file); + } + if (fname) { /* input is a path -- just open an apr_file_t */ - char* fname = PyString_AS_STRING(py_file); apr_err = apr_file_open(&apr_file, fname, APR_CREATE | APR_READ | APR_WRITE, APR_OS_DEFAULT, pool); @@ -2455,25 +2712,26 @@ apr_file_t *svn_swig_py_make_file(PyObje return NULL; } } - else if (PyFile_Check(py_file)) + else { - FILE *file; - apr_os_file_t osfile; + FILE *file = svn_swig_py_as_file(py_file); /* input is a file object -- convert to apr_file_t */ - file = PyFile_AsFile(py_file); + if (file != NULL) + { #ifdef WIN32 - osfile = (apr_os_file_t)_get_osfhandle(_fileno(file)); + apr_os_file_t osfile = (apr_os_file_t)_get_osfhandle(_fileno(file)); #else - osfile = (apr_os_file_t)fileno(file); + apr_os_file_t osfile = (apr_os_file_t)fileno(file); #endif - apr_err = apr_os_file_put(&apr_file, &osfile, O_CREAT | O_WRONLY, pool); - if (apr_err) - { - char buf[256]; - apr_strerror(apr_err, buf, sizeof(buf)); - PyErr_Format(PyExc_IOError, "apr_os_file_put failed: %s", buf); - return NULL; + apr_err = apr_os_file_put(&apr_file, &osfile, O_CREAT | O_WRONLY, pool); + if (apr_err) + { + char buf[256]; + apr_strerror(apr_err, buf, sizeof(buf)); + PyErr_Format(PyExc_IOError, "apr_os_file_put failed: %s", buf); + return NULL; + } } } return apr_file; @@ -2485,7 +2743,6 @@ read_handler_pyio(void *baton, char *buf { PyObject *result; PyObject *py_io = baton; - apr_size_t bytes; svn_error_t *err = SVN_NO_ERROR; if (py_io == Py_None) @@ -2502,10 +2759,17 @@ read_handler_pyio(void *baton, char *buf { err = callback_exception_error(); } - else if (PyString_Check(result)) + else if (PyBytes_Check(result)) { - bytes = PyString_GET_SIZE(result); - if (bytes > *len) + Py_ssize_t bytes; + char *result_str; + + if ( -1 == PyBytes_AsStringAndSize(result, &result_str, &bytes) + || result_str == NULL) + { + err = callback_exception_error(); + } + else if (bytes > *len) { err = callback_bad_return_error("Too many bytes"); } @@ -2513,12 +2777,12 @@ read_handler_pyio(void *baton, char *buf { /* Writeback, in case this was a short read, indicating EOF */ *len = bytes; - memcpy(buffer, PyString_AS_STRING(result), *len); + memcpy(buffer, result_str, *len); } } else { - err = callback_bad_return_error("Not a string"); + err = callback_bad_return_error("Not a bytes object"); } Py_XDECREF(result); svn_swig_py_release_py_lock(); @@ -2537,7 +2801,8 @@ write_handler_pyio(void *baton, const ch { svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallMethod(py_io, (char *)"write", - (char *)"s#", data, *len)) == NULL) + (char *) SVN_SWIG_BYTES_FMT "#", + data, *len)) == NULL) { err = callback_exception_error(); } @@ -2644,13 +2909,24 @@ void svn_swig_py_notify_func(void *baton PyObject *function = baton; PyObject *result; svn_error_t *err = SVN_NO_ERROR; + PyObject *exc, *exc_type, *exc_traceback; if (function == NULL || function == Py_None) return; svn_swig_py_acquire_py_lock(); + + /* As caller can't understand Python context and we can't notify if + Python call back function raise exception to caller, we must catch it + if it is occurred, and restore error indicator */ + PyErr_Fetch(&exc_type, &exc, &exc_traceback); + if ((result = PyObject_CallFunction(function, +#if IS_PY3 + (char *)"(yiiyiii)", +#else (char *)"(siisiii)", +#endif path, action, kind, mime_type, content_state, prop_state, @@ -2669,6 +2945,9 @@ void svn_swig_py_notify_func(void *baton /* Our error has no place to go. :-( */ svn_error_clear(err); + /* Also, restore error indicator */ + PyErr_Restore(exc_type, exc, exc_traceback); + svn_swig_py_release_py_lock(); } @@ -2680,12 +2959,18 @@ void svn_swig_py_notify_func2(void *bato PyObject *function = baton; PyObject *result; svn_error_t *err = SVN_NO_ERROR; + PyObject *exc, *exc_type, *exc_traceback; if (function == NULL || function == Py_None) return; svn_swig_py_acquire_py_lock(); + /* As caller can't understand Python context and we can't notify if + Python call back function raise exception to caller, we must catch it + if it is occurred, and restore error indicator */ + PyErr_Fetch(&exc_type, &exc, &exc_traceback); + if ((result = PyObject_CallFunction(function, (char *)"(O&O&)", make_ob_wc_notify, notify, @@ -2704,6 +2989,9 @@ void svn_swig_py_notify_func2(void *bato /* Our error has no place to go. :-( */ svn_error_clear(err); + /* Also, restore error indicator */ + PyErr_Restore(exc_type, exc, exc_traceback); + svn_swig_py_release_py_lock(); } @@ -2714,12 +3002,20 @@ void svn_swig_py_status_func(void *baton PyObject *function = baton; PyObject *result; svn_error_t *err = SVN_NO_ERROR; + PyObject *exc, *exc_type, *exc_traceback; if (function == NULL || function == Py_None) return; svn_swig_py_acquire_py_lock(); - if ((result = PyObject_CallFunction(function, (char *)"sO&", path, + + /* As caller can't understand Python context and we can't notify if + Python call back function raise exception to caller, we must catch it + if it is occurred, and restore error indicator */ + PyErr_Fetch(&exc_type, &exc, &exc_traceback); + + if ((result = PyObject_CallFunction(function, + (char *)SVN_SWIG_BYTES_FMT "O&", path, make_ob_wc_status, status)) == NULL) { err = callback_exception_error(); @@ -2735,6 +3031,73 @@ void svn_swig_py_status_func(void *baton /* Our error has no place to go. :-( */ svn_error_clear(err); + /* Also, restore error indicator */ + PyErr_Restore(exc_type, exc, exc_traceback); + + svn_swig_py_release_py_lock(); +} + +void svn_swig_py_client_status_func(void *baton, + const char *path, + const svn_client_status_t *status, + apr_pool_t *scratch_pool) +{ + PyObject *function = baton; + PyObject *result; + svn_error_t *err = SVN_NO_ERROR; + PyObject *exc, *exc_type, *exc_traceback; + + if (function == NULL || function == Py_None) + return; + + svn_swig_py_acquire_py_lock(); + + /* As caller can't understand Python context and we can't notify if + Python call back function raise exception to caller, we must catch it + if it is occurred, and restore error indicator */ + PyErr_Fetch(&exc_type, &exc, &exc_traceback); + + if (status == NULL) + { + result = PyObject_CallFunction(function, +#if IS_PY3 + (char *)"yOO&", +#else + (char *)"sOO&", +#endif + path, Py_None, + make_ob_pool, scratch_pool); + } + else + { + result = PyObject_CallFunction(function, +#if IS_PY3 + (char *)"yO&O&", +#else + (char *)"sO&O&", +#endif + path, + make_ob_client_status, status, + make_ob_pool, scratch_pool); + } + if (result == 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. :-( */ + svn_error_clear(err); + + /* Also, restore error indicator */ + PyErr_Restore(exc_type, exc, exc_traceback); + svn_swig_py_release_py_lock(); } @@ -2758,7 +3121,12 @@ svn_error_t *svn_swig_py_delta_path_driv "void *", NULL); - result = PyObject_CallFunction(function, (char *)"OsO&", + result = PyObject_CallFunction(function, +#if IS_PY3 + (char *)"OyO&", +#else + (char *)"OsO&", +#endif py_parent_baton, path, make_ob_pool, pool); @@ -2793,12 +3161,20 @@ void svn_swig_py_status_func2(void *bato PyObject *function = baton; PyObject *result; svn_error_t *err = SVN_NO_ERROR; + PyObject *exc, *exc_type, *exc_traceback; if (function == NULL || function == Py_None) return; svn_swig_py_acquire_py_lock(); - if ((result = PyObject_CallFunction(function, (char *)"sO&", path, + + /* As caller can't understand Python context and we can't notify if + Python call back function raise exception to caller, we must catch it + if it is occurred, and restore error indicator */ + PyErr_Fetch(&exc_type, &exc, &exc_traceback); + + if ((result = PyObject_CallFunction(function, + (char *)SVN_SWIG_BYTES_FMT "O&", path, make_ob_wc_status, status)) == NULL) { err = callback_exception_error(); @@ -2812,8 +3188,10 @@ void svn_swig_py_status_func2(void *bato } /* Our error has no place to go. :-( */ - if (err) - svn_error_clear(err); + svn_error_clear(err); + + /* Also, restore error indicator */ + PyErr_Restore(exc_type, exc, exc_traceback); svn_swig_py_release_py_lock(); } @@ -2902,7 +3280,7 @@ svn_error_t *svn_swig_py_fs_lock_callbac svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(py_callback, - (char *)"sO&O&O&", + (char *)SVN_SWIG_BYTES_FMT "O&O&O&", path, make_ob_lock, lock, make_ob_error, fs_err, @@ -2968,23 +3346,34 @@ svn_error_t *svn_swig_py_get_commit_log_ if (result == Py_None) { - Py_DECREF(result); *log_msg = NULL; err = SVN_NO_ERROR; } - else if (PyString_Check(result)) + else if (PyBytes_Check(result)) { - *log_msg = apr_pstrdup(pool, PyString_AS_STRING(result)); - Py_DECREF(result); + *log_msg = apr_pstrdup(pool, PyBytes_AsString(result)); err = SVN_NO_ERROR; } + else if (PyUnicode_Check(result)) + { + /* PyStr_AsUTF8() may cause UnicodeEncodeError, + but apr_pstrdup() allows NULL for s */ + if ((*log_msg = apr_pstrdup(pool, PyStr_AsUTF8(result))) == NULL) + { + err = callback_exception_error(); + } + else + { + err = SVN_NO_ERROR; + } + } else { - Py_DECREF(result); - err = callback_bad_return_error("Not a string"); + err = callback_bad_return_error("Not a bytes or str object"); } + Py_DECREF(result); - finished: +finished: svn_swig_py_release_py_lock(); return err; } @@ -3023,7 +3412,11 @@ svn_error_t *svn_swig_py_repos_authz_fun } if ((result = PyObject_CallFunction(function, +#if IS_PY3 + (char *)"OyO", +#else (char *)"OsO", +#endif py_root, path, py_pool)) == NULL) { err = callback_exception_error(); @@ -3060,7 +3453,7 @@ svn_error_t *svn_swig_py_repos_history_f svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(function, - (char *)"slO&", + (char *)SVN_SWIG_BYTES_FMT "lO&", path, revision, make_ob_pool, pool)) == NULL) { @@ -3187,7 +3580,7 @@ svn_error_t *svn_swig_py_proplist_receiv } result = PyObject_CallFunction(receiver, - (char *)"sOOO", + (char *)SVN_SWIG_BYTES_FMT "OOO", path, py_props, py_iprops, py_pool); if (result == NULL) { @@ -3247,7 +3640,11 @@ svn_error_t *svn_swig_py_log_receiver(vo } if ((result = PyObject_CallFunction(receiver, +#if IS_PY3 + (char *)"OlyyyO", +#else (char *)"OlsssO", +#endif chpaths, rev, author, date, msg, py_pool)) == NULL) { @@ -3325,7 +3722,7 @@ svn_error_t *svn_swig_py_info_receiver_f svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(receiver, - (char *)"sO&O&", + (char *)SVN_SWIG_BYTES_FMT "O&O&", path, make_ob_info, info, make_ob_pool, pool)) == NULL) { @@ -3394,7 +3791,12 @@ svn_error_t *svn_swig_py_client_blame_re svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(receiver, - (char *)"LlsssO&", + (char *) +#if IS_PY3 + "LlyyyO&", +#else + "LlsssO&", +#endif (PY_LONG_LONG)line_no, revision, author, date, line, make_ob_pool, pool)) == NULL) { @@ -3426,7 +3828,11 @@ svn_error_t *svn_swig_py_changelist_rece svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(receiver, +#if IS_PY3 + (char *)"yyO&", +#else (char *)"ssO&", +#endif path, changelist, make_ob_pool, pool)) == NULL) { @@ -3461,7 +3867,7 @@ svn_swig_py_auth_gnome_keyring_unlock_pr svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(function, - (char *)"sO&", + (char *)SVN_SWIG_BYTES_FMT "O&", keyring_name, make_ob_pool, pool)) == NULL) { @@ -3469,7 +3875,11 @@ svn_swig_py_auth_gnome_keyring_unlock_pr } else { - *keyring_passwd = make_string_from_ob(result, pool); + *keyring_passwd = make_string_from_ob_maybe_null(result, pool); + if (PyErr_Occurred()) + { + err = callback_exception_error(); + } Py_DECREF(result); } @@ -3497,7 +3907,11 @@ svn_swig_py_auth_simple_prompt_func(svn_ svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(function, +#if IS_PY3 + (char *)"yylO&", +#else (char *)"sslO&", +#endif realm, username, may_save, make_ob_pool, pool)) == NULL) { @@ -3548,7 +3962,7 @@ svn_swig_py_auth_username_prompt_func(sv svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(function, - (char *)"slO&", + (char *)SVN_SWIG_BYTES_FMT "lO&", realm, may_save, make_ob_pool, pool)) == NULL) { @@ -3600,7 +4014,8 @@ svn_swig_py_auth_ssl_server_trust_prompt svn_swig_py_acquire_py_lock(); - if ((result = PyObject_CallFunction(function, (char *)"slO&lO&", + if ((result = PyObject_CallFunction(function, + (char *)SVN_SWIG_BYTES_FMT "lO&lO&", realm, failures, make_ob_auth_ssl_server_cert_info, cert_info, may_save, make_ob_pool, pool)) == NULL) { @@ -3651,7 +4066,7 @@ svn_swig_py_auth_ssl_client_cert_prompt_ svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(function, - (char *)"slO&", + (char *)SVN_SWIG_BYTES_FMT "lO&", realm, may_save, make_ob_pool, pool)) == NULL) { @@ -3702,7 +4117,7 @@ svn_swig_py_auth_ssl_client_cert_pw_prom svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(function, - (char *)"slO&", + (char *)SVN_SWIG_BYTES_FMT "lO&", realm, may_save, make_ob_pool, pool)) == NULL) { @@ -3769,7 +4184,12 @@ svn_swig_py_config_auth_walk_func(svn_bo goto finished; } - if ((result = PyObject_CallFunction(function, (char *)"ssOO", + if ((result = PyObject_CallFunction(function, +#if IS_PY3 + (char *)"yyOO", +#else + (char *)"ssOO", +#endif cred_kind, realmstring, py_hash, py_scratch_pool)) == NULL) { @@ -3868,16 +4288,21 @@ ra_callbacks_get_wc_prop(void *baton, } if ((result = PyObject_CallFunction(py_callback, - (char *)"ssO&", path, name, +#if IS_PY3 + (char *)"yyO&", +#else + (char *)"ssO&", +#endif + path, name, make_ob_pool, pool)) == NULL) { err = callback_exception_error(); } else if (result != Py_None) { - char *buf; Py_ssize_t len; - if (PyString_AsStringAndSize(result, &buf, &len) == -1) + char *buf; + if (PyBytes_AsStringAndSize(result, &buf, &len) == -1) { err = callback_exception_error(); } @@ -3920,14 +4345,19 @@ ra_callbacks_push_or_set_wc_prop(const c goto finished; } - if ((py_value = PyString_FromStringAndSize(value->data, value->len)) == NULL) + if ((py_value = PyBytes_FromStringAndSize(value->data, value->len)) == NULL) { err = callback_exception_error(); goto finished; } if ((result = PyObject_CallFunction(py_callback, - (char *)"ssOO&", path, name, py_value, +#if IS_PY3 + (char *)"yyOO&", +#else + (char *)"ssOO&", +#endif + path, name, py_value, make_ob_pool, pool)) == NULL) { err = callback_exception_error(); @@ -3990,7 +4420,12 @@ ra_callbacks_invalidate_wc_props(void *b } if ((result = PyObject_CallFunction(py_callback, - (char *)"ssO&", path, name, +#if IS_PY3 + (char *)"yyO&", +#else + (char *)"ssO&", +#endif + path, name, make_ob_pool, pool)) == NULL) { err = callback_exception_error(); @@ -4012,11 +4447,17 @@ ra_callbacks_progress_func(apr_off_t pro { PyObject *callbacks = (PyObject *)baton; PyObject *py_callback, *py_progress, *py_total, *result; + PyObject *exc, *exc_type, *exc_traceback; py_progress = py_total = NULL; svn_swig_py_acquire_py_lock(); + /* As caller can't understand Python context and we can't notify if + Python call back function raise exception to caller, we must catch it + if it is occurred, and restore error indicator */ + PyErr_Fetch(&exc_type, &exc, &exc_traceback); + py_callback = PyObject_GetAttrString(callbacks, (char *)"progress_func"); if (py_callback == NULL) @@ -4056,6 +4497,9 @@ ra_callbacks_progress_func(apr_off_t pro Py_XDECREF(result); finished: + /* Restore error indicator */ + PyErr_Restore(exc_type, exc, exc_traceback); + Py_XDECREF(py_callback); Py_XDECREF(py_progress); Py_XDECREF(py_total); @@ -4119,7 +4563,7 @@ ra_callbacks_get_client_string(void *bat } else if (result != Py_None) { - if ((*name = PyString_AsString(result)) == NULL) + if ((*name = PyBytes_AsString(result)) == NULL) { err = callback_exception_error(); } @@ -4222,7 +4666,11 @@ svn_error_t *svn_swig_py_commit_callback svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(receiver, +#if IS_PY3 + (char *)"lyy", +#else (char *)"lss", +#endif new_revision, date, author)) == NULL) { err = callback_exception_error(); @@ -4274,7 +4722,7 @@ svn_error_t *svn_swig_py_ra_file_rev_han } if ((result = PyObject_CallFunction(handler, - (char *)"slOOO&", + (char *)SVN_SWIG_BYTES_FMT "lOOO&", path, rev, py_rev_props, py_prop_diffs, make_ob_pool, pool)) == NULL) { @@ -4320,7 +4768,7 @@ svn_error_t *svn_swig_py_ra_lock_callbac svn_swig_py_acquire_py_lock(); if ((result = PyObject_CallFunction(py_callback, - (char *)"sbO&O&O&", + (char *)SVN_SWIG_BYTES_FMT "bO&O&O&", path, do_lock, make_ob_lock, lock, make_ob_error, ra_err, @@ -4357,7 +4805,11 @@ static svn_error_t *reporter_set_path(vo if ((result = PyObject_CallMethod(py_reporter, (char *)"set_path", +#if IS_PY3 + (char *)"ylbyO&", +#else (char *)"slbsO&", +#endif path, revision, start_empty, lock_token, make_ob_pool, pool)) == NULL) @@ -4390,7 +4842,7 @@ static svn_error_t *reporter_delete_path if ((result = PyObject_CallMethod(py_reporter, (char *)"delete_path", - (char *)"sO&", + (char *)SVN_SWIG_BYTES_FMT "O&", path, make_ob_pool, pool)) == NULL) { @@ -4426,7 +4878,11 @@ static svn_error_t *reporter_link_path(v if ((result = PyObject_CallMethod(py_reporter, (char *)"link_path", +#if IS_PY3 + (char *)"yylbsO&", +#else (char *)"sslbsO&", +#endif path, url, revision, start_empty, lock_token, make_ob_pool, pool)) == NULL) @@ -4557,7 +5013,11 @@ wc_diff_callbacks2_file_changed_or_added } result = PyObject_CallFunction(py_callback, +#if IS_PY3 + (char *)"O&yyyllyyO&O&", +#else (char *)"O&sssllssO&O&", +#endif make_ob_wc_adm_access, adm_access, path, tmpfile1, tmpfile2, @@ -4680,7 +5140,11 @@ wc_diff_callbacks2_file_deleted(svn_wc_a } result = PyObject_CallFunction(py_callback, +#if IS_PY3 + (char *)"O&yyyyyO&", +#else (char *)"O&sssssO&", +#endif make_ob_wc_adm_access, adm_access, path, tmpfile1, tmpfile2, @@ -4734,7 +5198,11 @@ wc_diff_callbacks2_dir_added(svn_wc_adm_ } result = PyObject_CallFunction(py_callback, +#if IS_PY3 + (char *)"O&yl", +#else (char *)"O&sl", +#endif make_ob_wc_adm_access, adm_access, path, rev); if (result == NULL) @@ -4784,7 +5252,7 @@ wc_diff_callbacks2_dir_deleted(svn_wc_ad } result = PyObject_CallFunction(py_callback, - (char *)"O&s", + (char *)"O&" SVN_SWIG_BYTES_FMT, make_ob_wc_adm_access, adm_access, path); if (result == NULL) { @@ -4836,7 +5304,11 @@ wc_diff_callbacks2_dir_props_changed(svn } result = PyObject_CallFunction(py_callback, +#if IS_PY3 + (char *)"O&yO&O&", +#else (char *)"O&sO&O&", +#endif make_ob_wc_adm_access, adm_access, path, svn_swig_py_proparray_to_dict, propchanges, @@ -4888,11 +5360,21 @@ svn_swig_py_config_enumerator2(const cha PyObject *result; svn_error_t *err = SVN_NO_ERROR; svn_boolean_t c_result; + PyObject *exc, *exc_type, *exc_traceback; svn_swig_py_acquire_py_lock(); + /* As caller can't understand Python context and we can't notify if + Python call back function raise exception to caller, we must catch it + if it is occurred, and restore error indicator */ + PyErr_Fetch(&exc_type, &exc, &exc_traceback); + if ((result = PyObject_CallFunction(function, +#if IS_PY3 + (char *)"yyO&", +#else (char *)"ssO&", +#endif name, value, make_ob_pool, pool)) == NULL) @@ -4908,7 +5390,7 @@ svn_swig_py_config_enumerator2(const cha /* Any Python exception we might have pending must be cleared, because the SWIG wrapper will not check for it, and return a value with the exception still set. */ - PyErr_Clear(); + PyErr_Restore(exc_type, exc, exc_traceback); if (err) { @@ -4936,11 +5418,17 @@ svn_swig_py_config_section_enumerator2(c PyObject *result; svn_error_t *err = SVN_NO_ERROR; svn_boolean_t c_result; + PyObject *exc, *exc_type, *exc_traceback; svn_swig_py_acquire_py_lock(); + /* As caller can't understand Python context and we can't notify if + Python call back function raise exception to caller, we must catch it + if it is occurred, and restore error indicator */ + PyErr_Fetch(&exc_type, &exc, &exc_traceback); + if ((result = PyObject_CallFunction(function, - (char *)"sO&", + (char *)SVN_SWIG_BYTES_FMT "O&", name, make_ob_pool, pool)) == NULL) { @@ -4955,7 +5443,8 @@ svn_swig_py_config_section_enumerator2(c /* Any Python exception we might have pending must be cleared, because the SWIG wrapper will not check for it, and return a value with the exception still set. */ - PyErr_Clear(); + PyErr_Restore(exc_type, exc, exc_traceback); + if (err) {
Modified: subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h (original) +++ subversion/trunk/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h Mon Nov 4 05:59:36 2019 @@ -50,6 +50,12 @@ extern "C" { apr_status_t svn_swig_py_initialize(void); +/* Returns the provided python object as a FILE *object. + * Return the underlying FILE or NULL if the object is not a File instance. + */ +FILE *svn_swig_py_as_file(PyObject *pyfile); + + /* Functions to manage python's global interpreter lock */ void svn_swig_py_release_py_lock(void); @@ -101,6 +107,17 @@ void svn_swig_py_svn_exception(svn_error +/* Function to get char * representation of bytes/str object. This is + the replacement of typemap(in, parse="s") and typemap(in, parse="z") + to accept both of bytes object and str object, and it assumes to be + used from those typemaps only. + Note: type of return value should be char const *, however, as SWIG + produces variables for C function without 'const' modifier, to avoid + ton of cast in SWIG produced C code we drop it from return value + types as well */ +char *svn_swig_py_string_to_cstring(PyObject *input, int maybe_null, + const char * funcsym, const char * argsym); + /* helper function to convert an apr_hash_t* (char* -> svnstring_t*) to a Python dict */ PyObject *svn_swig_py_prophash_to_dict(apr_hash_t *hash); @@ -226,7 +243,10 @@ svn_swig_py_seq_to_array(PyObject *seq, apr_pool_t *pool); /* An svn_swig_py_object_unwrap_t that extracts a char pointer from a Python - string. */ + string. + + Note the lifetime of the returned string is tied to the provided Python + object. */ int svn_swig_py_unwrap_string(PyObject *source, void *destination, @@ -293,6 +313,13 @@ void svn_swig_py_status_func(void *baton const char *path, svn_wc_status_t *status); +/* a client status function that executes a Python function that is passed in + via the baton argument */ +void svn_swig_py_client_status_func(void *baton, + const char *path, + const svn_client_status_t *status, + apr_pool_t *scratch_pool); + /* a svn_delta_path_driver callback that executes a Python function that is passed in via the baton argument */ svn_error_t *svn_swig_py_delta_path_driver_cb_func(void **dir_baton, Modified: subversion/trunk/subversion/bindings/swig/python/svn/client.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/svn/client.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/svn/client.py (original) +++ subversion/trunk/subversion/bindings/swig/python/svn/client.py Mon Nov 4 05:59:36 2019 @@ -24,8 +24,8 @@ ###################################################################### from libsvn.client import * -from svn.core import _unprefix_names +from svn.core import _unprefix_names, _as_list _unprefix_names(locals(), 'svn_client_') _unprefix_names(locals(), 'SVN_CLIENT_') -__all__ = filter(lambda x: x.lower().startswith('svn_'), locals().keys()) +__all__ = [x for x in _as_list(locals()) if x.lower().startswith('svn_')] del _unprefix_names Modified: subversion/trunk/subversion/bindings/swig/python/svn/core.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/svn/core.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/svn/core.py (original) +++ subversion/trunk/subversion/bindings/swig/python/svn/core.py Mon Nov 4 05:59:36 2019 @@ -27,8 +27,8 @@ from libsvn.core import * import libsvn.core as _libsvncore import atexit as _atexit import sys -# __all__ is defined later, since some svn_* functions are implemented below. +# __all__ is defined later, since some svn_* functions are implemented below. class SubversionException(Exception): @@ -89,6 +89,20 @@ class SubversionException(Exception): child = cls(message, apr_err, child, file, line) return child +# This function is useful for common Python 2/3 code. It prevents the double +# memory hit of simply wrapping values/keys/items calls on dictionaries on +# python 2, but ensuring an independent list is returned in Python 3. +def _as_list(seq): + """Returns the given sequence or iterator as a list. + + If already a list, simply returns the list, otherwise a list is constructed + using the given object. + """ + if isinstance(seq, list): + return seq + + return list(seq) + def _cleanup_application_pool(): """Cleanup the application pool before exiting""" if application_pool and application_pool.valid(): @@ -96,7 +110,7 @@ def _cleanup_application_pool(): _atexit.register(_cleanup_application_pool) def _unprefix_names(symbol_dict, from_prefix, to_prefix = ''): - for name, value in symbol_dict.items(): + for name, value in _as_list(symbol_dict.items()): if name.startswith(from_prefix): symbol_dict[to_prefix + name[len(from_prefix):]] = value @@ -141,7 +155,7 @@ def svn_path_compare_paths(path1, path2) # Common prefix was skipped above, next character is compared to # determine order - return cmp(char1, char2) + return (char1 > char2) - (char1 < char2) def svn_mergeinfo_merge(mergeinfo, changes): return _libsvncore.svn_swig_mergeinfo_merge(mergeinfo, changes) @@ -171,7 +185,7 @@ class Stream: if not data: break chunks.append(data) - return ''.join(chunks) + return b''.join(chunks) # read the amount specified return svn_stream_read(self._stream, int(amt)) @@ -194,7 +208,7 @@ def secs_from_timestr(svn_datetime, pool # ### convert to a time_t; this requires intimate knowledge of # ### the apr_time_t type # ### aprtime is microseconds; turn it into seconds - return aprtime / 1000000 + return aprtime // 1000000 # ============================================================================ @@ -319,10 +333,10 @@ def run_app(func, *args, **kw): # 'run_app' # 'svn_uri__is_ancestor' # 'svn_tristate__from_word' 'svn_tristate__to_word' -__all__ = filter(lambda s: (s.startswith('svn_') - or s.startswith('SVN_') - or s.startswith('SVNSYNC_') - or s in ('Pool', 'SubversionException')) - and '__' not in s, - locals()) +__all__ = [s for s in _as_list(locals()) + if (s.startswith('svn_') + or s.startswith('SVN_') + or s.startswith('SVNSYNC_') + or s in ('Pool', 'SubversionException')) + and '__' not in s] Modified: subversion/trunk/subversion/bindings/swig/python/svn/delta.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/svn/delta.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/svn/delta.py (original) +++ subversion/trunk/subversion/bindings/swig/python/svn/delta.py Mon Nov 4 05:59:36 2019 @@ -24,10 +24,10 @@ ###################################################################### from libsvn.delta import * -from svn.core import _unprefix_names +from svn.core import _unprefix_names, _as_list _unprefix_names(locals(), 'svn_delta_') _unprefix_names(locals(), 'svn_txdelta_', 'tx_') -__all__ = filter(lambda x: x.lower().startswith('svn_'), locals().keys()) +__all__ = [x for x in _as_list(locals()) if x.lower().startswith('svn_')] del _unprefix_names class Editor: Modified: subversion/trunk/subversion/bindings/swig/python/svn/diff.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/svn/diff.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/svn/diff.py (original) +++ subversion/trunk/subversion/bindings/swig/python/svn/diff.py Mon Nov 4 05:59:36 2019 @@ -24,7 +24,7 @@ ###################################################################### from libsvn.diff import * -from svn.core import _unprefix_names +from svn.core import _unprefix_names, _as_list _unprefix_names(locals(), 'svn_diff_') -__all__ = filter(lambda x: x.lower().startswith('svn_'), locals().keys()) +__all__ = [x for x in _as_list(locals()) if x.lower().startswith('svn_')] del _unprefix_names Modified: subversion/trunk/subversion/bindings/swig/python/svn/fs.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/svn/fs.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/svn/fs.py (original) +++ subversion/trunk/subversion/bindings/swig/python/svn/fs.py Mon Nov 4 05:59:36 2019 @@ -24,10 +24,10 @@ ###################################################################### from libsvn.fs import * -from svn.core import _unprefix_names, Pool +from svn.core import _unprefix_names, Pool, _as_list _unprefix_names(locals(), 'svn_fs_') _unprefix_names(locals(), 'SVN_FS_') -__all__ = filter(lambda x: x.lower().startswith('svn_'), locals().keys()) +__all__ = [x for x in _as_list(locals()) if x.lower().startswith('svn_')] del _unprefix_names @@ -48,10 +48,30 @@ import svn.diff as _svndiff def entries(root, path, pool=None): "Call dir_entries returning a dictionary mappings names to IDs." e = dir_entries(root, path, pool) - for name, entry in e.items(): + for name, entry in _as_list(e.items()): e[name] = dirent_t_id_get(entry) return e +class _PopenStdoutWrapper(object): + "Private wrapper object of _subprocess.Popen.stdout to clean up sub process" + def __init__(self, pobject): + self._pobject = pobject + def __getattr__(self, name): + return getattr(self._pobject.stdout, name) + def close(self): + self._pobject.stdout.close() + if self._pobject.poll() is None: + self._pobject.terminate() + def __del__(self): + if not self.closed: + self.close() + if self._pobject.poll() is None: + self._pobject.terminate() + if _sys.hexversion >= 0x030300F0: + try: + self._pobject.wait(10) + except _subprocess.TimeoutExpired: + self._pobject.kill() class FileDiff: def __init__(self, root1, path1, root2, path2, pool=None, diffoptions=[]): @@ -124,7 +144,7 @@ class FileDiff: # open the pipe, and return the file object for reading from the child. p = _subprocess.Popen(cmd, stdout=_subprocess.PIPE, bufsize=-1, close_fds=_sys.platform != "win32") - return p.stdout + return _PopenStdoutWrapper(p) else: if self.difftemp is None: @@ -132,16 +152,16 @@ class FileDiff: with builtins.open(self.difftemp, "wb") as fp: diffopt = _svndiff.file_options_create() - diffobj = _svndiff.file_diff_2(self.tempfile1, - self.tempfile2, + diffobj = _svndiff.file_diff_2(self.tempfile1.encode('UTF-8'), + self.tempfile2.encode('UTF-8'), diffopt) _svndiff.file_output_unified4(fp, diffobj, - self.tempfile1, - self.tempfile2, + self.tempfile1.encode('UTF-8'), + self.tempfile2.encode('UTF-8'), None, None, - "utf8", + b"utf8", None, diffopt.show_c_function, diffopt.context_size, Modified: subversion/trunk/subversion/bindings/swig/python/svn/ra.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/svn/ra.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/svn/ra.py (original) +++ subversion/trunk/subversion/bindings/swig/python/svn/ra.py Mon Nov 4 05:59:36 2019 @@ -24,10 +24,10 @@ ###################################################################### from libsvn.ra import * -from svn.core import _unprefix_names +from svn.core import _unprefix_names, _as_list _unprefix_names(locals(), 'svn_ra_') _unprefix_names(locals(), 'SVN_RA_') -__all__ = filter(lambda x: x.lower().startswith('svn_'), locals().keys()) +__all__ = [x for x in _as_list(locals()) if x.lower().startswith('svn_')] del _unprefix_names class Callbacks: @@ -58,7 +58,7 @@ class Callbacks: svn.core.SVN_AUTH_PARAM_DEFAULT_PASSWORD, password) def open_tmp_file(self, pool): - path = '/'.join([self.wc, svn.wc.get_adm_dir(pool), 'tmp']) + path = b'/'.join([self.wc, svn.wc.get_adm_dir(pool), b'tmp']) (fd, fn) = tempfile.mkstemp(dir=path) os.close(fd) return fn Modified: subversion/trunk/subversion/bindings/swig/python/svn/repos.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/svn/repos.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/svn/repos.py (original) +++ subversion/trunk/subversion/bindings/swig/python/svn/repos.py Mon Nov 4 05:59:36 2019 @@ -24,10 +24,10 @@ ###################################################################### from libsvn.repos import * -from svn.core import _unprefix_names, Pool +from svn.core import _unprefix_names, Pool, _as_list _unprefix_names(locals(), 'svn_repos_') _unprefix_names(locals(), 'SVN_REPOS_') -__all__ = filter(lambda x: x.lower().startswith('svn_'), locals().keys()) +__all__ = [x for x in _as_list(locals()) if x.lower().startswith('svn_')] del _unprefix_names @@ -126,9 +126,9 @@ class ChangeCollector(_svndelta.Editor): self.notify_cb(change) def _make_base_path(self, parent_path, path): - idx = path.rfind('/') + idx = path.rfind(b'/') if parent_path: - parent_path = parent_path + '/' + parent_path = parent_path + b'/' if idx == -1: return parent_path + path return parent_path + path[idx+1:] @@ -142,7 +142,7 @@ class ChangeCollector(_svndelta.Editor): return root def open_root(self, base_revision, dir_pool=None): - return ('', '', self.base_rev) # dir_baton + return (b'', b'', self.base_rev) # dir_baton def delete_entry(self, path, revision, parent_baton, pool=None): base_path = self._make_base_path(parent_baton[1], path) @@ -281,9 +281,9 @@ class RevisionChangeCollector(ChangeColl ChangeCollector.__init__(self, fs_ptr, root, pool, notify_cb) def _make_base_path(self, parent_path, path): - idx = path.rfind('/') + idx = path.rfind(b'/') if idx == -1: - return parent_path + '/' + path + return parent_path + b'/' + path return parent_path + path[idx:] Modified: subversion/trunk/subversion/bindings/swig/python/svn/wc.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/svn/wc.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/svn/wc.py (original) +++ subversion/trunk/subversion/bindings/swig/python/svn/wc.py Mon Nov 4 05:59:36 2019 @@ -24,10 +24,10 @@ ###################################################################### from libsvn.wc import * -from svn.core import _unprefix_names +from svn.core import _unprefix_names, _as_list _unprefix_names(locals(), 'svn_wc_') _unprefix_names(locals(), 'SVN_WC_') -__all__ = filter(lambda x: x.lower().startswith('svn_'), locals().keys()) +__all__ = [x for x in _as_list(locals()) if x.lower().startswith('svn_')] del _unprefix_names Modified: subversion/trunk/subversion/bindings/swig/python/tests/auth.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/tests/auth.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/tests/auth.py (original) +++ subversion/trunk/subversion/bindings/swig/python/tests/auth.py Mon Nov 4 05:59:36 2019 @@ -27,87 +27,87 @@ class SubversionAuthTestCase(unittest.Te def test_open(self): baton = core.svn_auth_open([]) - self.assert_(baton is not None) + self.assertTrue(baton is not None) def test_set_parameter(self): baton = core.svn_auth_open([]) - core.svn_auth_set_parameter(baton, "name", "somedata") - core.svn_auth_set_parameter(baton, "name", None) - core.svn_auth_set_parameter(baton, "name", 2) - core.svn_auth_set_parameter(baton, "name", + core.svn_auth_set_parameter(baton, b"name", b"somedata") + core.svn_auth_set_parameter(baton, b"name", None) + core.svn_auth_set_parameter(baton, b"name", 2) + core.svn_auth_set_parameter(baton, b"name", core.svn_auth_ssl_server_cert_info_t()) def test_invalid_cred_kind(self): baton = core.svn_auth_open([]) self.assertRaises(core.SubversionException, lambda: core.svn_auth_first_credentials( - "unknown", "somerealm", baton)) + b"unknown", b"somerealm", baton)) def test_credentials_get_username(self): def myfunc(realm, maysave, pool): - self.assertEquals("somerealm", realm) + self.assertEqual(b"somerealm", realm) username_cred = core.svn_auth_cred_username_t() - username_cred.username = "bar" + username_cred.username = b"bar" username_cred.may_save = False return username_cred baton = core.svn_auth_open([core.svn_auth_get_username_prompt_provider(myfunc, 1)]) creds = core.svn_auth_first_credentials( - core.SVN_AUTH_CRED_USERNAME, "somerealm", baton) - self.assert_(creds is not None) + core.SVN_AUTH_CRED_USERNAME, b"somerealm", baton) + self.assertTrue(creds is not None) def test_credentials_get_simple(self): def myfunc(realm, username, may_save, pool): - self.assertEquals("somerealm", realm) + self.assertEqual(b"somerealm", realm) simple_cred = core.svn_auth_cred_simple_t() - simple_cred.username = "mijnnaam" - simple_cred.password = "geheim" + simple_cred.username = b"mijnnaam" + simple_cred.password = b"geheim" simple_cred.may_save = False return simple_cred baton = core.svn_auth_open([core.svn_auth_get_simple_prompt_provider(myfunc, 1)]) creds = core.svn_auth_first_credentials( - core.SVN_AUTH_CRED_SIMPLE, "somerealm", baton) - self.assert_(creds is not None) + core.SVN_AUTH_CRED_SIMPLE, b"somerealm", baton) + self.assertTrue(creds is not None) def test_credentials_get_ssl_client_cert(self): def myfunc(realm, may_save, pool): - self.assertEquals("somerealm", realm) + self.assertEqual(b"somerealm", realm) ssl_cred = core.svn_auth_cred_ssl_client_cert_t() - ssl_cred.cert_file = "my-certs-file" + ssl_cred.cert_file = b"my-certs-file" ssl_cred.may_save = False return ssl_cred baton = core.svn_auth_open([core.svn_auth_get_ssl_client_cert_prompt_provider(myfunc, 1)]) creds = core.svn_auth_first_credentials( - core.SVN_AUTH_CRED_SSL_CLIENT_CERT, "somerealm", baton) - self.assert_(creds is not None) + core.SVN_AUTH_CRED_SSL_CLIENT_CERT, b"somerealm", baton) + self.assertTrue(creds is not None) def test_credentials_get_ssl_client_cert_pw(self): def myfunc(realm, may_save, pool): - self.assertEquals("somerealm", realm) + self.assertEqual(b"somerealm", realm) ssl_cred_pw = core.svn_auth_cred_ssl_client_cert_pw_t() - ssl_cred_pw.password = "supergeheim" + ssl_cred_pw.password = b"supergeheim" ssl_cred_pw.may_save = False return ssl_cred_pw baton = core.svn_auth_open([core.svn_auth_get_ssl_client_cert_pw_prompt_provider(myfunc, 1)]) creds = core.svn_auth_first_credentials( - core.SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, "somerealm", baton) - self.assert_(creds is not None) + core.SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, b"somerealm", baton) + self.assertTrue(creds is not None) def test_credentials_get_ssl_server_trust(self): def myfunc(realm, failures, cert_info, may_save, pool): - self.assertEquals("somerealm", realm) + self.assertEqual(b"somerealm", realm) ssl_trust = core.svn_auth_cred_ssl_server_trust_t() ssl_trust.accepted_failures = 0 ssl_trust.may_save = False return ssl_trust baton = core.svn_auth_open([core.svn_auth_get_ssl_server_trust_prompt_provider(myfunc)]) core.svn_auth_set_parameter(baton, core.SVN_AUTH_PARAM_SSL_SERVER_FAILURES, - "2") + b"2") cert_info = core.svn_auth_ssl_server_cert_info_t() core.svn_auth_set_parameter(baton, core.SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, cert_info) creds = core.svn_auth_first_credentials( - core.SVN_AUTH_CRED_SSL_SERVER_TRUST, "somerealm", baton) - self.assert_(creds is not None) + core.SVN_AUTH_CRED_SSL_SERVER_TRUST, b"somerealm", baton) + self.assertTrue(creds is not None) def suite(): return unittest.defaultTestLoader.loadTestsFromTestCase( Modified: subversion/trunk/subversion/bindings/swig/python/tests/checksum.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/swig/python/tests/checksum.py?rev=1869354&r1=1869353&r2=1869354&view=diff ============================================================================== --- subversion/trunk/subversion/bindings/swig/python/tests/checksum.py (original) +++ subversion/trunk/subversion/bindings/swig/python/tests/checksum.py Mon Nov 4 05:59:36 2019 @@ -29,7 +29,7 @@ class ChecksumTestCases(unittest.TestCas val = svn.core.svn_checksum_create(kind) check_val = svn.core.svn_checksum_to_cstring_display(val) - self.assertTrue(isinstance(check_val, str), + self.assertTrue(isinstance(check_val, bytes), "Type of digest not string") self.assertEqual(len(check_val), 2*expected_length, "Length of digest does not match kind")