[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: [PATCH] Allow "cease invocation" return from Python callback for repos.svn_repos_history2()

From: Alexey Neyman <stilor_at_att.net>
Date: Thu, 18 Mar 2010 16:26:28 -0700

On Thursday 18 March 2010 03:50:35 pm Роман Донченко wrote:
> Alexey Neyman <stilor_at_att.net> писал в своём письме Thu, 18 Mar 2010
>
> 21:51:53 +0300:
> > On Wednesday 17 March 2010 04:59:34 pm Роман Донченко wrote:
> >> Alexey Neyman <stilor_at_att.net> писал в своём письме Wed, 17 Mar 2010
> >>
> >> 00:05:01 +0300:
> >> > Hi all,
> >> >
> >> > The svn_repos_history2() function allows the history_func() to
> >> > return a special error, SVN_ERR_CEASE_INVOCATION, to stop the
> >> > search. This is not supported in Python bindings, though: attempt
> >> > to return core.SVN_ERR_CEASE_INVOCATION from the history receiver
> >> > results in an exception:
> >> >
> >> > def history_lookup(path, rev, pool):
> >> > return core.SVN_ERR_CEASE_INVOCATION
> >> >
> >> > repos.svn_repos_history2(..., history_lookup, ...)
> >> >
> >> > svn.core.SubversionException: ('Python callback returned an
> >> > invalid object', 20014)
> >> >
> >> > Indeed, svn_swig_py_repos_history_func() only expects a return of
> >> > None. This patch allows the callback to return
> >> > core.SVN_ERR_CEASE_INVOCATION. Currently, this is only used in
> >> > svn_repos_history2() - apparently, it's the only function that
> >> > supports SVN_ERR_CEASE_INVOCATION. However, there is a FIXME at
> >> > least in copyfrom_info_receiver() in libsvn_client/log.c, stating
> >> > that other callbacks may eventually be able to return
> >> > SVN_ERR_CEASE_INVOCATION as well.
> >>
> >> Good idea, but I think that the callback should signal an error the
> >> same way the Subversion functions do it - namely, by throwing
> >> core.SubversionException. The wrapper would then translate the
> >> exception's fields into an svn_error_t. It looks like tweaking
> >> callback_exception_error is the most obvious way to do that. You
> >> don't even have to special-case SVN_ERR_CEASE_INVOCATION, just copy
> >> whatever code the exception had into the error.
> >
> > I think that may be beyond my current knowledge of Python's C API...
> > I tried that:
> >
> > /* Return a Subversion error about a failed callback. */
> > static svn_error_t *callback_exception_error(void)
> > {
> > PyObject *err = PyErr_Occurred();
> > PyObject *svn_module, *exc_class;
> > PyObject *message, *apr_err;
> >
> > if ((svn_module = PyImport_ImportModule((char *)"svn.core")) ==
> > NULL) goto finished;
> > if ((exc_class = PyObject_GetAttrString(svn_module,
> > (char *)"SubversionException")) == NULL)
> > goto finished;
> > if (PyErr_GivenExceptionMatches(exc_class, err))
> > {
> > message = PyObject_GetAttrString(err, (char *)"message");
> > apr_err = PyObject_GetAttrString(err, (char *)"apr_err");
> > if (message && apr_err && PyString_Check(message)
> > && PyInt_Check(apr_err))
> > {
> > return svn_error_create(PyInt_AsLong(apr_err), NULL,
> > PyString_AsString(message));
> > }
> > }
> >
> > finished:
> > return svn_error_create(SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL,
> > "Python callback raised an exception");
> > }
> >
> > First, it cannot find "apr_err" in the instance of
> > core.Subversion_Exception; PyObject_GetAttrString(err, (char
> > *)"apr_err") returns NULL.
> >
> > And, it produces the following error:
> >
> > SystemError: NULL result without error in PyObject_Call
> >
> > Can you help? What's wrong with that code?
>
> Hmm. How are you testing this? I'd write a test script, but I'm short
> on time, and you probably already have one. 8=]

Yes, please use the attached pre-commit script. Then:

$ svnadmin create /tmp/c.repo
$ cp <...this attachment...> /tmp/c.repo/hooks/pre-commit
$ svn mkdir -m "1" file:///tmp/c.repo/trunk
$ svn cp -m "2" file:///tmp/c.repo/trunk file:///tmp/c.repo/copied-dir
$ svn mkdir -m "3" file:///tmp/c.repo/copied-dir/newdir

With stock 1.6.6, it produces the following traceback:

-------------------------------------------------------------------------
svn: Commit blocked by pre-commit hook (exit code 1) with output:
Traceback (most recent call last):
  File "/tmp/c.repo/hooks/pre-commit", line 35, in <module>
    core.run_app(verify, sys.argv[1], sys.argv[2])
  File "/usr/local/lib/svn-python/svn/core.py", line 281, in run_app
    return func(application_pool, *args, **kw)
  File "/tmp/c.repo/hooks/pre-commit", line 31, in verify
    repos.svn_repos_replay2(txn_root, "", -1, True, e_ptr, e_baton, None,
pool)
  File "/usr/local/lib/svn-python/libsvn/repos.py", line 311, in
svn_repos_replay2
    return apply(_repos.svn_repos_replay2, args)
  File "/tmp/c.repo/hooks/pre-commit", line 22, in open_directory
    repos.svn_repos_history2(self.fs_ptr, path, history_lookup, None, 0,
self.base_rev, True, self.pool)
  File "/usr/local/lib/svn-python/libsvn/repos.py", line 407, in
svn_repos_history2
    return apply(_repos.svn_repos_history2, args)
  File "/tmp/c.repo/hooks/pre-commit", line 21, in history_lookup
    raise core.SubversionException(apr_err=core.SVN_ERR_CEASE_INVOCATION,
message="Hi from history_lookup")
svn.core.SubversionException: ('Hi from history_lookup', 200021)
-------------------------------------------------------------------------

Changed to the following code (difference from previous version is that it
Py_DECREF's everything):

-------------------------------------------------------------------------
static svn_error_t *callback_exception_error(void)
{
  PyObject *err = PyErr_Occurred();
  PyObject *svn_module = NULL, *exc_class = NULL;
  PyObject *message = NULL, *apr_err = NULL;
  svn_error_t *rv = NULL;

  if ((svn_module = PyImport_ImportModule((char *)"svn.core")) == NULL)
    goto finished;
  if ((exc_class = PyObject_GetAttrString(svn_module,
        (char *)"SubversionException")) == NULL)
    goto finished;
  if (PyErr_GivenExceptionMatches(exc_class, err))
    {
      apr_err = PyObject_GetAttrString(err, (char *)"apr_err");
      message = PyObject_GetAttrString(err, (char *)"message");
      if (message && apr_err && PyString_Check(message) &&
          PyInt_Check(apr_err))
        rv = svn_error_create(PyInt_AsLong(apr_err), NULL,
            PyString_AsString(message));
    }
finished:
  Py_XDECREF(svn_module);
  Py_XDECREF(exc_class);
  Py_XDECREF(apr_err);
  Py_XDECREF(message);
  return rv ? rv : svn_error_create(SVN_ERR_SWIG_PY_EXCEPTION_SET, NULL,
                          "Python callback raised an exception");
}
-------------------------------------------------------------------------

With this, pre-commit produces this traceback:

-------------------------------------------------------------------------
svn: Commit blocked by pre-commit hook (exit code 1) with output:
Traceback (most recent call last):
  File "/tmp/c.repo/hooks/pre-commit", line 35, in <module>
    core.run_app(verify, sys.argv[1], sys.argv[2])
  File "/usr/local/lib/svn-python/svn/core.py", line 281, in run_app
    return func(application_pool, *args, **kw)
  File "/tmp/c.repo/hooks/pre-commit", line 31, in verify
    repos.svn_repos_replay2(txn_root, "", -1, True, e_ptr, e_baton, None,
pool)
  File "/usr/local/lib/svn-python/libsvn/repos.py", line 311, in
svn_repos_replay2
    return apply(_repos.svn_repos_replay2, args)
SystemError: NULL result without error in PyObject_Call
-------------------------------------------------------------------------

Hope that helps.

Regards,
Alexey.

Received on 2010-03-19 00:26:59 CET

This is an archived mail posted to the Subversion Dev mailing list.