Author: svn-role Date: Fri Oct 20 04:00:04 2023 New Revision: 1913135 URL: http://svn.apache.org/viewvc?rev=1913135&view=rev Log: Merge the r1912500 group from trunk:
* r1912500, r1912501, r1912502, r1912503, r1912515, r1912517, r1912691 swig-py: Use pure Python objects as edit/parse_fns3 and decendant batons. Justification: Bug fix. Issue #4916, #4917, #4918 Votes: +1: futatuki +0: dsahlberg (not enough experience for +1, but looks good) Modified: subversion/branches/1.14.x/ (props changed) subversion/branches/1.14.x/STATUS subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/branches/1.14.x/subversion/bindings/swig/python/svn/delta.py subversion/branches/1.14.x/subversion/bindings/swig/python/svn/repos.py subversion/branches/1.14.x/subversion/bindings/swig/python/tests/delta.py subversion/branches/1.14.x/subversion/bindings/swig/python/tests/repository.py subversion/branches/1.14.x/subversion/bindings/swig/svn_delta.i subversion/branches/1.14.x/subversion/bindings/swig/svn_repos.i Propchange: subversion/branches/1.14.x/ ------------------------------------------------------------------------------ Merged /subversion/trunk:r1912500-1912503,1912515,1912517,1912691 Modified: subversion/branches/1.14.x/STATUS URL: http://svn.apache.org/viewvc/subversion/branches/1.14.x/STATUS?rev=1913135&r1=1913134&r2=1913135&view=diff ============================================================================== --- subversion/branches/1.14.x/STATUS (original) +++ subversion/branches/1.14.x/STATUS Fri Oct 20 04:00:04 2023 @@ -41,11 +41,3 @@ Veto-blocked changes: Approved changes: ================= - - * r1912500, r1912501, r1912502, r1912503, r1912515, r1912517, r1912691 - swig-py: Use pure Python objects as edit/parse_fns3 and decendant batons. - Justification: - Bug fix. Issue #4916, #4917, #4918 - Votes: - +1: futatuki - +0: dsahlberg (not enough experience for +1, but looks good) Modified: subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c URL: http://svn.apache.org/viewvc/subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c?rev=1913135&r1=1913134&r2=1913135&view=diff ============================================================================== --- subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c (original) +++ subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c Fri Oct 20 04:00:04 2023 @@ -1762,44 +1762,97 @@ static svn_error_t *type_conversion_erro /*** Editor Wrapping ***/ -/* this baton is used for the editor, directory, and file batons. */ -typedef struct item_baton { - PyObject *editor; /* the editor handling the callbacks */ - PyObject *baton; /* the dir/file baton (or NULL for edit baton) */ - apr_pool_t *pool; /* top-level pool */ -} item_baton; - -static item_baton *make_baton(apr_pool_t *pool, - PyObject *editor, - PyObject *baton) -{ - item_baton *newb = apr_palloc(pool, sizeof(*newb)); - - /* Note: We steal the caller's reference to 'baton'. */ - Py_INCREF(editor); - newb->editor = editor; - newb->baton = baton; - newb->pool = pool; +static PyObject * +make_baton(apr_pool_t *pool, PyObject *parent, PyObject *baton) +{ + PyObject *newb; + newb = PyObject_CallMethod(parent, "make_decendant", "O&O", + make_ob_pool, pool, baton); + /* We always borrow the reference in ancestor's dict during the C API + processing, so that we never leak the reference even the API aborted + by some error */ + Py_XDECREF(newb); return newb; } -static svn_error_t *close_baton(void *baton, - const char *method) +/* Get 'editor' and 'baton' from _ItemBaton instance. The caller + should be within a Python thread lock. */ +static svn_error_t * +unwrap_item_baton(PyObject **editor, PyObject **baton, PyObject *item_baton) { - item_baton *ib = baton; + svn_error_t *err; + + if ((*editor = PyObject_GetAttrString(item_baton, "editor")) == NULL) + { + err = callback_exception_error(); + *baton = NULL; + goto finished; + } + if ((*baton = PyObject_GetAttrString(item_baton, "baton")) == NULL) + { + Py_CLEAR(*editor); + err = callback_exception_error(); + goto finished; + } + err = SVN_NO_ERROR; + finished: + Py_XDECREF(*editor); + Py_XDECREF(*baton); + return err; +} + +/* Get 'editor', 'baton', 'pool' from _ItemBaton instance. The caller + should be within a Python thread lock. */ +static svn_error_t * +unwrap_item_baton_with_pool(PyObject **editor, PyObject **baton, + PyObject **py_pool, PyObject *item_baton) +{ + svn_error_t *err; + + if ((err = unwrap_item_baton(editor, baton, item_baton)) != SVN_NO_ERROR) + { + *py_pool = NULL; + goto finished; + } + if ((*py_pool = PyObject_GetAttrString(item_baton, "pool")) == NULL) + { + err = callback_exception_error(); + *editor = NULL; + *baton = NULL; + goto finished; + } + err = SVN_NO_ERROR; + finished: + Py_XDECREF(*py_pool); + return err; +} + +static svn_error_t * +close_baton(void *baton, const char *method, svn_boolean_t without_item) +{ + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } + if (without_item) + { + baton_item = NULL; + } /* If there is no baton object, then it is an edit_baton, and we should not bother to pass an object. Note that we still shove a NULL onto the stack, but the format specified just won't reference it. */ /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)method, - ib->baton ? (char *)"(O)" : NULL, - ib->baton)) == NULL) + if ((result = PyObject_CallMethod(editor, (char *)method, + baton_item ? (char *)"(O)" : NULL, + baton_item)) == NULL) { err = callback_exception_error(); goto finished; @@ -1808,19 +1861,24 @@ static svn_error_t *close_baton(void *ba /* there is no return value, so just toss this object (probably Py_None) */ Py_DECREF(result); - /* Release the editor object */ - Py_DECREF(ib->editor); - - /* We're now done with the baton. Since there isn't really a free, all - we need to do is note that its objects are no longer referenced by - the baton. */ - Py_XDECREF(ib->baton); - -#ifdef SVN_DEBUG - ib->editor = ib->baton = NULL; -#endif - - err = SVN_NO_ERROR; + /* We're now done with the baton. Release it from ancestor's dict */ + if (PyObject_HasAttrString(ib, "release_self")) + { + /* Get reference for ib, because following function call remove + ib object from ancestor's dict, which we borrow the reference */ + Py_INCREF(ib); + result = PyObject_CallMethod(ib, "release_self", NULL, NULL); + /* Now we can release the reference safely */ + Py_DECREF(ib); + if (result == NULL) + { + err = callback_exception_error(); + goto finished; + } + /* there is no return value, so just toss this object + (probably Py_None) */ + Py_DECREF(result); + } finished: svn_swig_py_release_py_lock(); @@ -1831,14 +1889,19 @@ static svn_error_t *set_target_revision( svn_revnum_t target_revision, apr_pool_t *pool) { - item_baton *ib = edit_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = edit_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"set_target_revision", + if ((result = PyObject_CallMethod(editor, (char *)"set_target_revision", (char *)"l", target_revision)) == NULL) { err = callback_exception_error(); @@ -1859,14 +1922,19 @@ static svn_error_t *open_root(void *edit apr_pool_t *dir_pool, void **root_baton) { - item_baton *ib = edit_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = edit_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"open_root", + if ((result = PyObject_CallMethod(editor, (char *)"open_root", (char *)"lO&", base_revision, make_ob_pool, dir_pool)) == NULL) { @@ -1874,11 +1942,15 @@ static svn_error_t *open_root(void *edit goto finished; } - /* make_baton takes our 'result' reference */ - *root_baton = make_baton(dir_pool, ib->editor, result); + if ((*root_baton = make_baton(dir_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -1888,16 +1960,21 @@ static svn_error_t *delete_entry(const c void *parent_baton, apr_pool_t *pool) { - item_baton *ib = parent_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"delete_entry", + if ((result = PyObject_CallMethod(editor, (char *)"delete_entry", (char *)SVN_SWIG_BYTES_FMT "lOO&", - path, revision, ib->baton, + path, revision, baton_item, make_ob_pool, pool)) == NULL) { err = callback_exception_error(); @@ -1920,20 +1997,25 @@ static svn_error_t *add_directory(const apr_pool_t *dir_pool, void **child_baton) { - item_baton *ib = parent_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"add_directory", + if ((result = PyObject_CallMethod(editor, (char *)"add_directory", #if IS_PY3 (char *)"yOylO&", #else (char *)"sOslO&", #endif - path, ib->baton, + path, baton_item, copyfrom_path, copyfrom_revision, make_ob_pool, dir_pool)) == NULL) { @@ -1941,11 +2023,15 @@ static svn_error_t *add_directory(const goto finished; } - /* make_baton takes our 'result' reference */ - *child_baton = make_baton(dir_pool, ib->editor, result); + if ((*child_baton = make_baton(dir_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -1956,27 +2042,36 @@ static svn_error_t *open_directory(const apr_pool_t *dir_pool, void **child_baton) { - item_baton *ib = parent_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"open_directory", + if ((result = PyObject_CallMethod(editor, (char *)"open_directory", (char *)SVN_SWIG_BYTES_FMT "OlO&", - path, ib->baton, base_revision, + path, baton_item, base_revision, make_ob_pool, dir_pool)) == NULL) { err = callback_exception_error(); goto finished; } - /* make_baton takes our 'result' reference */ - *child_baton = make_baton(dir_pool, ib->editor, result); + if ((*child_baton = make_baton(dir_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -1986,20 +2081,25 @@ static svn_error_t *change_dir_prop(void const svn_string_t *value, apr_pool_t *pool) { - item_baton *ib = dir_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = dir_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"change_dir_prop", + if ((result = PyObject_CallMethod(editor, (char *)"change_dir_prop", #if IS_PY3 (char *)"Oyy#O&", #else (char *)"Oss#O&", #endif - ib->baton, name, + baton_item, name, value ? value->data : NULL, (Py_ssize_t) (value ? value->len : 0), make_ob_pool, pool)) == NULL) @@ -2020,7 +2120,7 @@ static svn_error_t *change_dir_prop(void static svn_error_t *close_directory(void *dir_baton, apr_pool_t *pool) { - return close_baton(dir_baton, "close_directory"); + return close_baton(dir_baton, "close_directory", FALSE); } static svn_error_t *add_file(const char *path, @@ -2030,20 +2130,25 @@ static svn_error_t *add_file(const char apr_pool_t *file_pool, void **file_baton) { - item_baton *ib = parent_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"add_file", + if ((result = PyObject_CallMethod(editor, (char *)"add_file", #if IS_PY3 (char *)"yOylO&", #else (char *)"sOslO&", #endif - path, ib->baton, + path, baton_item, copyfrom_path, copyfrom_revision, make_ob_pool, file_pool)) == NULL) { @@ -2051,12 +2156,16 @@ static svn_error_t *add_file(const char goto finished; } - /* make_baton takes our 'result' reference */ - *file_baton = make_baton(file_pool, ib->editor, result); + if ((*file_baton = make_baton(file_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2067,27 +2176,36 @@ static svn_error_t *open_file(const char apr_pool_t *file_pool, void **file_baton) { - item_baton *ib = parent_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"open_file", + if ((result = PyObject_CallMethod(editor, (char *)"open_file", (char *)SVN_SWIG_BYTES_FMT "OlO&", - path, ib->baton, base_revision, + path, baton_item, base_revision, make_ob_pool, file_pool)) == NULL) { err = callback_exception_error(); goto finished; } - /* make_baton takes our 'result' reference */ - *file_baton = make_baton(file_pool, ib->editor, result); + if ((*file_baton = make_baton(file_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2095,12 +2213,19 @@ static svn_error_t *open_file(const char static svn_error_t *window_handler(svn_txdelta_window_t *window, void *baton) { - PyObject *handler = baton; - PyObject *result; - svn_error_t *err; + PyObject *editor = NULL, *handler = NULL; + PyObject *ib = baton; + PyObject *result = NULL; + int is_last_call = FALSE; + svn_error_t *err = SVN_NO_ERROR; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &handler, ib)) != SVN_NO_ERROR) + { + is_last_call = TRUE; + goto finished; + } if (window == NULL) { /* the last call; it closes the handler */ @@ -2108,9 +2233,8 @@ static svn_error_t *window_handler(svn_t /* invoke the handler with None for the window */ /* ### python doesn't have 'const' on the format */ result = PyObject_CallFunction(handler, (char *)"O", Py_None); + is_last_call = TRUE; - /* we no longer need to refer to the handler object */ - Py_DECREF(handler); } else { @@ -2123,14 +2247,40 @@ static svn_error_t *window_handler(svn_t if (result == NULL) { err = callback_exception_error(); + is_last_call = TRUE; goto finished; } - - /* there is no return value, so just toss this object (probably Py_None) */ - Py_DECREF(result); - err = SVN_NO_ERROR; + else + { + /* there is no return value, so just toss this object + (probably Py_None) */ + Py_DECREF(result); + err = SVN_NO_ERROR; + } finished: + if (is_last_call) + { + /* now we should release the handler object */ + if (PyObject_HasAttrString(ib, "release_self")) + { + /* Get reference for ib, because following function call remove + ib object from ancestor's dict, which we borrow the reference */ + Py_INCREF(ib); + result = PyObject_CallMethod(ib, "release_self", NULL, NULL); + /* Now we can release the reference safely */ + Py_DECREF(ib); + if (result == NULL) + { + if (err == SVN_NO_ERROR) + { + err = callback_exception_error(); + } + } + Py_XDECREF(result); + } + } + svn_swig_py_release_py_lock(); return err; } @@ -2141,20 +2291,25 @@ static svn_error_t *apply_textdelta(void svn_txdelta_window_handler_t *handler, void **h_baton) { - item_baton *ib = file_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = file_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"apply_textdelta", + if ((result = PyObject_CallMethod(editor, (char *)"apply_textdelta", #if IS_PY3 (char *)"(Oy)", #else (char *)"(Os)", #endif - ib->baton, + baton_item, base_checksum)) == NULL) { err = callback_exception_error(); @@ -2173,15 +2328,21 @@ static svn_error_t *apply_textdelta(void } else { - /* return the thunk for invoking the handler. the baton takes our - 'result' reference, which is the handler. */ + /* return the thunk for invoking the handler. the baton creates + new reference of our 'result' reference, which is the handler, + so we release it even if no error. */ *handler = window_handler; - *h_baton = result; + if ((*h_baton = make_baton(pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2191,20 +2352,25 @@ static svn_error_t *change_file_prop(voi const svn_string_t *value, apr_pool_t *pool) { - item_baton *ib = file_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = file_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"change_file_prop", + if ((result = PyObject_CallMethod(editor, (char *)"change_file_prop", #if IS_PY3 (char *)"Oyy#O&", #else (char *)"Oss#O&", #endif - ib->baton, name, + baton_item, name, value ? value->data : NULL, (Py_ssize_t) (value ? value->len : 0), make_ob_pool, pool)) == NULL) @@ -2226,20 +2392,25 @@ static svn_error_t *close_file(void *fil const char *text_checksum, apr_pool_t *pool) { - item_baton *ib = file_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = file_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"close_file", + if ((result = PyObject_CallMethod(editor, (char *)"close_file", #if IS_PY3 (char *)"(Oy)", #else (char *)"(Os)", #endif - ib->baton, + baton_item, text_checksum)) == NULL) { err = callback_exception_error(); @@ -2249,14 +2420,24 @@ static svn_error_t *close_file(void *fil /* there is no return value, so just toss this object (probably Py_None) */ Py_DECREF(result); - /* We're now done with the baton. Since there isn't really a free, all - we need to do is note that its objects are no longer referenced by - the baton. */ - Py_XDECREF(ib->baton); - -#ifdef SVN_DEBUG - ib->editor = ib->baton = NULL; -#endif + /* We're now done with the baton. Release it from ancestor's dict */ + if (PyObject_HasAttrString(ib, "release_self")) + { + /* Get reference for ib, because following function call remove + ib object from ancestor's dict, which we borrow the reference */ + Py_INCREF(ib); + result = PyObject_CallMethod(ib, "release_self", NULL, NULL); + /* Now we can release the reference safely */ + Py_DECREF(ib); + if (result == NULL) + { + err = callback_exception_error(); + goto finished; + } + /* there is no return value, so just toss this object + (probably Py_None) */ + Py_DECREF(result); + } err = SVN_NO_ERROR; @@ -2268,18 +2449,16 @@ static svn_error_t *close_file(void *fil static svn_error_t *close_edit(void *edit_baton, apr_pool_t *pool) { - return close_baton(edit_baton, "close_edit"); + return close_baton(edit_baton, "close_edit", TRUE); } static svn_error_t *abort_edit(void *edit_baton, apr_pool_t *pool) { - return close_baton(edit_baton, "abort_edit"); + return close_baton(edit_baton, "abort_edit", TRUE); } void svn_swig_py_make_editor(const svn_delta_editor_t **editor, - void **edit_baton, - PyObject *py_editor, apr_pool_t *pool) { svn_delta_editor_t *thunk_editor = svn_delta_default_editor(pool); @@ -2300,7 +2479,6 @@ void svn_swig_py_make_editor(const svn_d thunk_editor->abort_edit = abort_edit; *editor = thunk_editor; - *edit_baton = make_baton(pool, py_editor, NULL); } @@ -2310,14 +2488,19 @@ static svn_error_t *parse_fn3_magic_head void *parse_baton, apr_pool_t *pool) { - item_baton *ib = parse_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parse_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"magic_header_record", + if ((result = PyObject_CallMethod(editor, (char *)"magic_header_record", (char *)"lO&", version, make_ob_pool, pool)) == NULL) { @@ -2339,14 +2522,19 @@ static svn_error_t *parse_fn3_uuid_recor void *parse_baton, apr_pool_t *pool) { - item_baton *ib = parse_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parse_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"uuid_record", + if ((result = PyObject_CallMethod(editor, (char *)"uuid_record", (char *)SVN_SWIG_BYTES_FMT "O&", uuid, make_ob_pool, pool)) == NULL) { @@ -2369,14 +2557,19 @@ static svn_error_t *parse_fn3_new_revisi void *parse_baton, apr_pool_t *pool) { - item_baton *ib = parse_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parse_baton; + PyObject *result = NULL; PyObject *tmp; svn_error_t *err; svn_swig_py_acquire_py_lock(); - if ((result = PyObject_CallMethod(ib->editor, (char *)"new_revision_record", + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } + if ((result = PyObject_CallMethod(editor, (char *)"new_revision_record", (char *)"O&O&", svn_swig_py_stringhash_to_dict, headers, make_ob_pool, pool)) == NULL) { @@ -2384,11 +2577,15 @@ static svn_error_t *parse_fn3_new_revisi goto finished; } - /* make_baton takes our 'result' reference */ - *revision_baton = make_baton(pool, ib->editor, result); + if ((*revision_baton = make_baton(pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2399,26 +2596,35 @@ static svn_error_t *parse_fn3_new_node_r void *revision_baton, apr_pool_t *pool) { - item_baton *ib = revision_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = revision_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); - if ((result = PyObject_CallMethod(ib->editor, (char *)"new_node_record", + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } + if ((result = PyObject_CallMethod(editor, (char *)"new_node_record", (char *)"O&OO&", svn_swig_py_stringhash_to_dict, headers, - ib->baton, + baton_item, make_ob_pool, pool)) == NULL) { err = callback_exception_error(); goto finished; } - /* make_baton takes our 'result' reference */ - *node_baton = make_baton(pool, ib->editor, result); + if ((*node_baton = make_baton(pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2428,20 +2634,25 @@ static svn_error_t *parse_fn3_set_revisi const char *name, const svn_string_t *value) { - item_baton *ib = revision_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = revision_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"set_revision_property", + if ((result = PyObject_CallMethod(editor, (char *)"set_revision_property", #if IS_PY3 (char *)"Oyy#", #else (char *)"Oss#", #endif - ib->baton, name, + baton_item, name, value ? value->data : NULL, (Py_ssize_t) (value ? value->len : 0))) == NULL) @@ -2464,20 +2675,25 @@ static svn_error_t *parse_fn3_set_node_p const char *name, const svn_string_t *value) { - item_baton *ib = node_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = node_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"set_node_property", + if ((result = PyObject_CallMethod(editor, (char *)"set_node_property", #if IS_PY3 (char *)"Oyy#", #else (char *)"Oss#", #endif - ib->baton, name, + baton_item, name, value ? value->data : NULL, (Py_ssize_t) (value ? value->len : 0))) == NULL) @@ -2499,16 +2715,21 @@ static svn_error_t *parse_fn3_set_node_p static svn_error_t *parse_fn3_delete_node_property(void *node_baton, const char *name) { - item_baton *ib = node_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = node_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"delete_node_property", + if ((result = PyObject_CallMethod(editor, (char *)"delete_node_property", (char *)"O" SVN_SWIG_BYTES_FMT, - ib->baton, name)) == NULL) + baton_item, name)) == NULL) { err = callback_exception_error(); goto finished; @@ -2526,15 +2747,20 @@ static svn_error_t *parse_fn3_delete_nod static svn_error_t *parse_fn3_remove_node_props(void *node_baton) { - item_baton *ib = node_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = node_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"remove_node_props", - (char *)"(O)", ib->baton)) == NULL) + if ((result = PyObject_CallMethod(editor, (char *)"remove_node_props", + (char *)"(O)", baton_item)) == NULL) { err = callback_exception_error(); goto finished; @@ -2553,15 +2779,22 @@ static svn_error_t *parse_fn3_remove_nod static svn_error_t *parse_fn3_set_fulltext(svn_stream_t **stream, void *node_baton) { - item_baton *ib = node_baton; + PyObject *editor = NULL, *baton_item = NULL, *py_pool = NULL; + PyObject *ib = node_baton; PyObject *result = NULL; + apr_pool_t *pool; svn_error_t *err = SVN_NO_ERROR; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton_with_pool(&editor, &baton_item, &py_pool, + ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"set_fulltext", - (char *)"(O)", ib->baton)) == NULL) + if ((result = PyObject_CallMethod(editor, (char *)"set_fulltext", + (char *)"(O)", baton_item)) == NULL) { err = callback_exception_error(); goto finished; @@ -2574,9 +2807,15 @@ static svn_error_t *parse_fn3_set_fullte } else { + if (svn_swig_ConvertPtrString(py_pool, (void **)&pool, + "apr_pool_t *") == -1) + { + err = type_conversion_error("apr_pool_t *"); + goto finished; + } /* create a stream from the IO object. it will increment the reference on the 'result'. */ - *stream = svn_swig_py_make_stream(result, ib->pool); + *stream = svn_swig_py_make_stream(result, pool); if (*stream == NULL) { err = callback_exception_error(); @@ -2593,19 +2832,27 @@ finished: } -static svn_error_t *parse_fn3_apply_textdelta(svn_txdelta_window_handler_t *handler, - void **handler_baton, - void *node_baton) +static svn_error_t * +parse_fn3_apply_textdelta(svn_txdelta_window_handler_t *handler, + void **handler_baton, + void *node_baton) { - item_baton *ib = node_baton; + PyObject *editor = NULL, *baton_item = NULL, *py_pool = NULL; + PyObject *ib = node_baton; + apr_pool_t *pool; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton_with_pool(&editor, &baton_item, &py_pool, + ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"apply_textdelta", - (char *)"(O)", ib->baton)) == NULL) + if ((result = PyObject_CallMethod(editor, (char *)"apply_textdelta", + (char *)"(O)", baton_item)) == NULL) { err = callback_exception_error(); goto finished; @@ -2623,10 +2870,21 @@ static svn_error_t *parse_fn3_apply_text } else { - /* return the thunk for invoking the handler. the baton takes our - 'result' reference, which is the handler. */ + /* return the thunk for invoking the handler. the baton creates + new reference of our 'result' reference, which is the handler, + so we release it even if no error. */ *handler = window_handler; - *handler_baton = result; + if (svn_swig_ConvertPtrString(py_pool, (void **)&pool, + "apr_pool_t *") == -1) + { + err = type_conversion_error("apr_pool_t *"); + goto finished; + } + if ((*handler_baton = make_baton(pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } } err = SVN_NO_ERROR; @@ -2639,13 +2897,13 @@ static svn_error_t *parse_fn3_apply_text static svn_error_t *parse_fn3_close_node(void *node_baton) { - return close_baton(node_baton, "close_node"); + return close_baton(node_baton, "close_node", FALSE); } static svn_error_t *parse_fn3_close_revision(void *revision_baton) { - return close_baton(revision_baton, "close_revision"); + return close_baton(revision_baton, "close_revision", FALSE); } @@ -2665,26 +2923,11 @@ static const svn_repos_parse_fns3_t thun parse_fn3_close_revision }; -static apr_status_t -svn_swig_py_parse_fns3_destroy(void *parse_baton) -{ - close_baton(parse_baton, "_close_dumpstream"); - return APR_SUCCESS; -} - void svn_swig_py_make_parse_fns3(const svn_repos_parse_fns3_t **parse_fns3, - void **parse_baton, - PyObject *py_parse_fns3, apr_pool_t *pool) { *parse_fns3 = &thunk_parse_fns3_vtable; - *parse_baton = make_baton(pool, py_parse_fns3, NULL); - - /* Dump stream vtable does not provide a method which is called right before - the end of the parsing (similar to close_edit/abort_edit in delta editor). - Thus, register a pool clean-up routine to release this parse baton. */ - apr_pool_cleanup_register(pool, *parse_baton, svn_swig_py_parse_fns3_destroy, - apr_pool_cleanup_null); + return; } Modified: subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h URL: http://svn.apache.org/viewvc/subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h?rev=1913135&r1=1913134&r2=1913135&view=diff ============================================================================== --- subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h (original) +++ subversion/branches/1.14.x/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h Fri Oct 20 04:00:04 2023 @@ -268,14 +268,10 @@ svn_swig_py_unwrap_struct_ptr(PyObject * /* make an editor that "thunks" from C callbacks up to Python */ void svn_swig_py_make_editor(const svn_delta_editor_t **editor, - void **edit_baton, - PyObject *py_editor, apr_pool_t *pool); /* make a parse vtable that "thunks" from C callbacks up to Python */ void svn_swig_py_make_parse_fns3(const svn_repos_parse_fns3_t **parse_fns3, - void **parse_baton, - PyObject *py_parse_fns3, apr_pool_t *pool); apr_file_t *svn_swig_py_make_file(PyObject *py_file, Modified: subversion/branches/1.14.x/subversion/bindings/swig/python/svn/delta.py URL: http://svn.apache.org/viewvc/subversion/branches/1.14.x/subversion/bindings/swig/python/svn/delta.py?rev=1913135&r1=1913134&r2=1913135&view=diff ============================================================================== --- subversion/branches/1.14.x/subversion/bindings/swig/python/svn/delta.py (original) +++ subversion/branches/1.14.x/subversion/bindings/swig/python/svn/delta.py Fri Oct 20 04:00:04 2023 @@ -77,5 +77,6 @@ class Editor: pass -def make_editor(editor, pool=None): - return svn_swig_py_make_editor(editor, pool) +def make_editor(editor, pool=None, baton=None): + from libsvn.delta import _AncBaton + return svn_swig_py_make_editor(pool), _AncBaton(editor, pool, baton) Modified: subversion/branches/1.14.x/subversion/bindings/swig/python/svn/repos.py URL: http://svn.apache.org/viewvc/subversion/branches/1.14.x/subversion/bindings/swig/python/svn/repos.py?rev=1913135&r1=1913134&r2=1913135&view=diff ============================================================================== --- subversion/branches/1.14.x/subversion/bindings/swig/python/svn/repos.py (original) +++ subversion/branches/1.14.x/subversion/bindings/swig/python/svn/repos.py Fri Oct 20 04:00:04 2023 @@ -336,5 +336,16 @@ class ParseFns3: pass -def make_parse_fns3(parse_fns3, pool=None): - return svn_swig_py_make_parse_fns3(parse_fns3, pool) +def make_parse_fns3(parse_fns3, pool=None, baton=None): + from libsvn.delta import _AncBaton + + class _ParseBaton(_AncBaton): + # Drive _close_dumpstream method when the instance is deleted. + # For backward compatibility before Subversion 1.15, we call it even if + # the instance would not be used by C API, or the C API would cause + # some error. + def __del__(self): + self.editor._close_dumpstream() + + parse_baton = _ParseBaton(parse_fns3, pool, baton) + return svn_swig_py_make_parse_fns3(pool), parse_baton Modified: subversion/branches/1.14.x/subversion/bindings/swig/python/tests/delta.py URL: http://svn.apache.org/viewvc/subversion/branches/1.14.x/subversion/bindings/swig/python/tests/delta.py?rev=1913135&r1=1913134&r2=1913135&view=diff ============================================================================== --- subversion/branches/1.14.x/subversion/bindings/swig/python/tests/delta.py (original) +++ subversion/branches/1.14.x/subversion/bindings/swig/python/tests/delta.py Fri Oct 20 04:00:04 2023 @@ -21,6 +21,7 @@ import unittest, setup_path import os import tempfile +import weakref import svn.delta import svn.core from sys import version_info # For Python version check @@ -117,6 +118,19 @@ class DeltaTestCase(unittest.TestCase): # Check that the ops inherit the window's pool self.assertEqual(window.ops[0]._parent_pool, window._parent_pool) + def testMakeEditorLeak(self): + """Issue 4916, check ref-count leak on svn.delta.make_editor()""" + pool = svn.core.Pool() + editor = svn.delta.Editor() + editor_ref = weakref.ref(editor) + e_ptr, e_baton = svn.delta.make_editor(editor, pool) + del e_ptr, e_baton + self.assertNotEqual(editor_ref(), None) + del pool + self.assertNotEqual(editor_ref(), None) + del editor + self.assertEqual(editor_ref(), None) + def suite(): return unittest.defaultTestLoader.loadTestsFromTestCase(DeltaTestCase) Modified: subversion/branches/1.14.x/subversion/bindings/swig/python/tests/repository.py URL: http://svn.apache.org/viewvc/subversion/branches/1.14.x/subversion/bindings/swig/python/tests/repository.py?rev=1913135&r1=1913134&r2=1913135&view=diff ============================================================================== --- subversion/branches/1.14.x/subversion/bindings/swig/python/tests/repository.py (original) +++ subversion/branches/1.14.x/subversion/bindings/swig/python/tests/repository.py Fri Oct 20 04:00:04 2023 @@ -18,11 +18,11 @@ # under the License. # # -import unittest, setup_path, os, sys +import unittest, setup_path, os, sys, weakref from sys import version_info # For Python version check from io import BytesIO from svn import core, repos, fs, delta -from svn.core import SubversionException +from svn.core import SubversionException, Pool import utils class ChangeReceiver(delta.Editor): @@ -40,9 +40,20 @@ class ChangeReceiver(delta.Editor): return textdelta_handler class DumpStreamParser(repos.ParseFns3): - def __init__(self): + def __init__(self, stream=None, pool=None): repos.ParseFns3.__init__(self) + self.stream = stream self.ops = [] + # for leak checking only. If the parse_fns3 object holds some proxy + # object allocated from 'pool' or the 'pool' itself, the 'pool' is not + # destroyed until the parse_fns3 object is removed. + self.pool = pool + def _close_dumpstream(self): + if self.stream: + self.stream.close() + self.stream = None + if self.pool: + self.pool = None def magic_header_record(self, version, pool=None): self.ops.append((b"magic-header", version)) def uuid_record(self, uuid, pool=None): @@ -74,6 +85,76 @@ class DumpStreamParser(repos.ParseFns3): self.ops.append((b"set-fulltext", node_baton[0], node_baton[1])) return None +class BatonCollector(repos.ChangeCollector): + """A ChangeCollector with collecting batons, too""" + def __init__(self, fs_ptr, root, pool=None, notify_cb=None): + repos.ChangeCollector.__init__(self, fs_ptr, root, pool, notify_cb) + self.batons = [] + self.close_called = False + self.abort_called = False + + def open_root(self, base_revision, dir_pool=None): + bt = repos.ChangeCollector.open_root(self, base_revision, dir_pool) + self.batons.append((b'dir baton', b'', bt, sys.getrefcount(bt))) + return bt + + def add_directory(self, path, parent_baton, + copyfrom_path, copyfrom_revision, dir_pool=None): + bt = repos.ChangeCollector.add_directory(self, path, parent_baton, + copyfrom_path, + copyfrom_revision, + dir_pool) + self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt))) + return bt + + def open_directory(self, path, parent_baton, base_revision, + dir_pool=None): + bt = repos.ChangeCollector.open_directory(self, path, parent_baton, + base_revision, dir_pool) + self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt))) + return bt + + def add_file(self, path, parent_baton, + copyfrom_path, copyfrom_revision, file_pool=None): + bt = repos.ChangeCollector.add_file(self, path, parent_baton, + copyfrom_path, copyfrom_revision, + file_pool) + self.batons.append((b'file baton', path, bt, sys.getrefcount(bt))) + return bt + + def open_file(self, path, parent_baton, base_revision, file_pool=None): + bt = repos.ChangeCollector.open_file(self, path, parent_baton, + base_revision, file_pool) + self.batons.append((b'file baton', path, bt, sys.getrefcount(bt))) + return bt + + def close_edit(self, pool=None): + self.close_called = True + return + + def abort_edit(self, pool=None): + self.abort_called = True + return + +class BatonCollectorErrorOnClose(BatonCollector): + """Same as BatonCollector, but raises an Exception when close the + file/dir specfied by error_path""" + def __init__(self, fs_ptr, root, pool=None, notify_cb=None, error_path=b''): + BatonCollector.__init__(self, fs_ptr, root, pool, notify_cb) + self.error_path = error_path + + def close_directory(self, dir_baton): + if dir_baton[0] == self.error_path: + raise SubversionException('A Dummy Exception!', core.SVN_ERR_BASE) + else: + BatonCollector.close_directory(self, dir_baton) + + def close_file(self, file_baton, text_checksum): + if file_baton[0] == self.error_path: + raise SubversionException('A Dummy Exception!', core.SVN_ERR_BASE) + else: + return BatonCollector.close_file(self, file_baton, text_checksum) + def _authz_callback(root, path, pool): "A dummy authz callback which always returns success." @@ -175,13 +256,15 @@ class SubversionRepositoryTestCase(unitt def is_cancelled(): self.cancel_calls += 1 return None + pool = Pool() + subpool = Pool(pool) dump_path = os.path.join(os.path.dirname(sys.argv[0]), "trac/versioncontrol/tests/svnrepos.dump") stream = open(dump_path, 'rb') - dsp = DumpStreamParser() - ptr, baton = repos.make_parse_fns3(dsp) + dsp = DumpStreamParser(stream, subpool) + dsp_ref = weakref.ref(dsp) + ptr, baton = repos.make_parse_fns3(dsp, subpool) repos.parse_dumpstream3(stream, ptr, baton, False, is_cancelled) - stream.close() self.assertEqual(self.cancel_calls, 76) expected_list = [ (b"magic-header", 2), @@ -226,6 +309,13 @@ class SubversionRepositoryTestCase(unitt # the comparison list gets too long. self.assertEqual(dsp.ops[:len(expected_list)], expected_list) + # _close_dumpstream should be invoked after 'baton' is removed. + self.assertEqual(False, stream.closed) + del ptr, baton, subpool, dsp + self.assertEqual(True, stream.closed) + # Issue SVN-4918 + self.assertEqual(None, dsp_ref()) + def test_parse_fns3_invalid_set_fulltext(self): class DumpStreamParserSubclass(DumpStreamParser): def set_fulltext(self, node_baton): @@ -290,6 +380,53 @@ class SubversionRepositoryTestCase(unitt repos.dir_delta(prev_root, b'', b'', this_root, b'', e_ptr, e_baton, _authz_callback, 1, 1, 0, 0) + def test_delta_editor_leak_with_change_collector(self): + pool = Pool() + subpool = Pool(pool) + root = fs.revision_root(self.fs, self.rev, subpool) + editor = repos.ChangeCollector(self.fs, root, subpool) + editor_ref = weakref.ref(editor) + e_ptr, e_baton = delta.make_editor(editor, subpool) + repos.replay(root, e_ptr, e_baton, subpool) + + fs.close_root(root) + del root + self.assertNotEqual(None, editor_ref()) + + del e_ptr, e_baton, editor + del subpool + self.assertEqual(None, editor_ref()) + + def test_replay_batons_refcounts(self): + """Issue SVN-4917: check ref-count of batons created and used in callbacks""" + root = fs.revision_root(self.fs, self.rev) + editor = BatonCollector(self.fs, root) + e_ptr, e_baton = delta.make_editor(editor) + repos.replay(root, e_ptr, e_baton) + for baton in editor.batons: + self.assertEqual(sys.getrefcount(baton[2]), 2, + "leak on baton %s after replay without errors" + % repr(baton)) + del e_baton + self.assertEqual(sys.getrefcount(e_ptr), 2, + "leak on editor baton after replay without errors") + + editor = BatonCollectorErrorOnClose(self.fs, root, + error_path=b'branches/v1x') + e_ptr, e_baton = delta.make_editor(editor) + self.assertRaises(SubversionException, repos.replay, root, e_ptr, e_baton) + batons = editor.batons + # As svn_repos_replay calls neither close_edit callback nor abort_edit + # if an error has occured during processing, references of Python objects + # in decendant batons may live until e_baton is deleted. + del e_baton + for baton in batons: + self.assertEqual(sys.getrefcount(baton[2]), 2, + "leak on baton %s after replay with an error" + % repr(baton)) + self.assertEqual(sys.getrefcount(e_ptr), 2, + "leak on editor baton after replay with an error") + def test_retrieve_and_change_rev_prop(self): """Test playing with revprops""" self.assertEqual(repos.fs_revision_prop(self.repos, self.rev, b"svn:log", Modified: subversion/branches/1.14.x/subversion/bindings/swig/svn_delta.i URL: http://svn.apache.org/viewvc/subversion/branches/1.14.x/subversion/bindings/swig/svn_delta.i?rev=1913135&r1=1913134&r2=1913135&view=diff ============================================================================== --- subversion/branches/1.14.x/subversion/bindings/swig/svn_delta.i (original) +++ subversion/branches/1.14.x/subversion/bindings/swig/svn_delta.i Fri Oct 20 04:00:04 2023 @@ -68,8 +68,6 @@ ### There must be a cleaner way to implement this? ### Maybe follow Ruby by wrapping it where passing an editor? */ void svn_swig_py_make_editor(const svn_delta_editor_t **editor, - void **edit_baton, - PyObject *py_editor, apr_pool_t *pool); #endif @@ -207,6 +205,49 @@ void _ops_get(int *num_ops, const svn_tx #ifdef SWIGPYTHON %pythoncode %{ +# Baton container class for editor/parse_fns3 batons and their decendants. +class _ItemBaton: + def __init__(self, editor, pool, baton=None): + self.pool = pool if pool else libsvn.core.svn_pool_create() + self.baton = baton + self.editor = editor + + def get_ancestor(self): + raise NotImplementedError + + def make_decendant(self, pool, baton=None): + return _DecBaton(self, pool, baton) + + +class _DecBaton(_ItemBaton): + def __init__(self, parent, pool, baton=None): + import weakref + _ItemBaton.__init__(self, parent.editor, pool, baton) + self._anc = weakref.ref(parent.get_ancestor()) + self._anc().hold_baton(self) + + def get_ancestor(self): + return self._anc() + + def release_self(self): + self._anc().release_baton(self) + + +class _AncBaton(_ItemBaton): + def __init__(self, editor, pool, baton=None): + _ItemBaton.__init__(self, editor, pool, baton) + self._dec = {} # hold decendant batons. + + def get_ancestor(self): + return self + + def hold_baton(self, baton): + self._dec[id(baton)] = baton + + def release_baton(self, baton): + del self._dec[id(baton)] + + # This function is for backwards compatibility only. # Use svn_txdelta_window_t.ops instead. svn_txdelta_window_t_ops_get = svn_txdelta_window_t._ops_get Modified: subversion/branches/1.14.x/subversion/bindings/swig/svn_repos.i URL: http://svn.apache.org/viewvc/subversion/branches/1.14.x/subversion/bindings/swig/svn_repos.i?rev=1913135&r1=1913134&r2=1913135&view=diff ============================================================================== --- subversion/branches/1.14.x/subversion/bindings/swig/svn_repos.i (original) +++ subversion/branches/1.14.x/subversion/bindings/swig/svn_repos.i Fri Oct 20 04:00:04 2023 @@ -152,8 +152,6 @@ svn_error_t *svn_repos_dump_fs2(svn_repo #ifdef SWIGPYTHON /* Make swig wrap this function for us, to allow making a vtable in python */ void svn_swig_py_make_parse_fns3(const svn_repos_parse_fns3_t **parse_fns3, - void **parse_baton, - PyObject *py_parse_fns3, apr_pool_t *pool); #endif