<commit_before>/* Copyright © 2008 <NAME> <<EMAIL>>
 * -*- coding: utf-8 -*-
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <stdbool.h>
#include <Python.h>
#include <apr_general.h>
#include <svn_types.h>
#include <svn_ra.h>
#include <svn_path.h>
#include <apr_file_io.h>

#include <structmember.h>

#include "editor.h"
#include "util.h"
#include "ra.h"

static PyObject *busy_exc;

PyAPI_DATA(PyTypeObject) Reporter_Type;
PyAPI_DATA(PyTypeObject) RemoteAccess_Type;
PyAPI_DATA(PyTypeObject) AuthProvider_Type;
PyAPI_DATA(PyTypeObject) TxDeltaWindowHandler_Type;

static svn_error_t *py_commit_callback(const svn_commit_info_t *commit_info, void *baton, apr_pool_t *pool)
{
	PyObject *fn = (PyObject *)baton, *ret;

	if (fn == Py_None)
		return NULL;

	ret = PyObject_CallFunction(fn, "izz", 
								commit_info->revision, commit_info->date, 
								commit_info->author);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
	return NULL;
}

static PyObject *pyify_lock(const svn_lock_t *lock)
{
    Py_RETURN_NONE; /* FIXME */
}

static svn_error_t *py_lock_func (void *baton, const char *path, int do_lock, 
                           const svn_lock_t *lock, svn_error_t *ra_err, 
                           apr_pool_t *pool)
{
    PyObject *py_ra_err = Py_None, *ret, *py_lock;
    if (ra_err != NULL) {
        py_ra_err = PyErr_NewSubversionException(ra_err);
	}
	py_lock = pyify_lock(lock);
    ret = PyObject_CallFunction((PyObject *)baton, "zbOO", path, do_lock, 
						  py_lock, py_ra_err);
	Py_DECREF(py_lock);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
	return NULL;
}

static void py_progress_func(apr_off_t progress, apr_off_t total, void *baton, apr_pool_t *pool)
{
    PyObject *fn = (PyObject *)baton, *ret;
    if (fn == Py_None) {
        return;
	}
	ret = PyObject_CallFunction(fn, "ll", progress, total);
	/* TODO: What to do with exceptions raised here ? */
	if (ret == NULL)
		return;
	Py_DECREF(ret);
}


typedef struct {
	PyObject_HEAD
    const svn_ra_reporter2_t *reporter;
    void *report_baton;
    apr_pool_t *pool;
	void (*done_cb)(void *baton);
	void *done_baton;
} ReporterObject;

static PyObject *reporter_set_path(PyObject *self, PyObject *args)
{
	char *path; 
	svn_revnum_t revision; 
	bool start_empty; 
	char *lock_token = NULL;
	ReporterObject *reporter = (ReporterObject *)self;

	if (!PyArg_ParseTuple(args, "slb|z", &path, &revision, &start_empty, 
						  &lock_token))
		return NULL;

    if (!check_error(reporter->reporter->set_path(reporter->report_baton, 
												  path, revision, start_empty, 
					 lock_token, reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyObject *reporter_delete_path(PyObject *self, PyObject *args)
{
	ReporterObject *reporter = (ReporterObject *)self;
	char *path;
	if (!PyArg_ParseTuple(args, "s", &path))
		return NULL;

	if (!check_error(reporter->reporter->delete_path(reporter->report_baton, 
													path, reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyObject *reporter_link_path(PyObject *self, PyObject *args)
{
	char *path, *url;
	svn_revnum_t revision;
	bool start_empty;
	char *lock_token = NULL;
	ReporterObject *reporter = (ReporterObject *)self;

	if (!PyArg_ParseTuple(args, "sslb|z", &path, &url, &revision, &start_empty, &lock_token))
		return NULL;

	if (!check_error(reporter->reporter->link_path(reporter->report_baton, path, url, 
				revision, start_empty, lock_token, reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyObject *reporter_finish(PyObject *self)
{
	ReporterObject *reporter = (ReporterObject *)self;

	if (reporter->done_cb != NULL)
		reporter->done_cb(reporter->done_baton);

	if (!check_error(reporter->reporter->finish_report(reporter->report_baton, 
													  reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyObject *reporter_abort(PyObject *self)
{
	ReporterObject *reporter = (ReporterObject *)self;
	
	if (reporter->done_cb != NULL)
		reporter->done_cb(reporter->done_baton);

	if (!check_error(reporter->reporter->abort_report(reporter->report_baton, 
													 reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyMethodDef reporter_methods[] = {
	{ "abort", (PyCFunction)reporter_abort, METH_NOARGS, NULL },
	{ "finish", (PyCFunction)reporter_finish, METH_NOARGS, NULL },
	{ "link_path", (PyCFunction)reporter_link_path, METH_VARARGS, NULL },
	{ "set_path", (PyCFunction)reporter_set_path, METH_VARARGS, NULL },
	{ "delete_path", (PyCFunction)reporter_delete_path, METH_VARARGS, NULL },
	{ NULL, }
};

static void reporter_dealloc(PyObject *self)
{
	ReporterObject *reporter = (ReporterObject *)self;
	/* FIXME: Warn the user if abort_report/finish_report wasn't called? */
	apr_pool_destroy(reporter->pool);
	PyObject_Del(self);
}

PyTypeObject Reporter_Type = {
	PyObject_HEAD_INIT(NULL) 0,
	.tp_name = "ra.Reporter",
	.tp_basicsize = sizeof(ReporterObject),
	.tp_methods = reporter_methods,
	.tp_dealloc = reporter_dealloc,
};

/**
 * Get libsvn_ra version information.
 *
 * :return: tuple with major, minor, patch version number and tag.
 */
static PyObject *version(PyObject *self)
{
    const svn_version_t *ver = svn_ra_version();
    return Py_BuildValue("(iiis)", ver->major, ver->minor, 
						 ver->patch, ver->tag);
}

static svn_error_t *py_cb_editor_set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)edit_baton, *ret;

	ret = PyObject_CallMethod(self, "set_target_revision", "l", target_revision);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **root_baton)
{
    PyObject *self = (PyObject *)edit_baton, *ret;
    *root_baton = NULL;
    ret = PyObject_CallMethod(self, "open_root", "l", base_revision);
	if (ret == NULL)
		return py_svn_error();
    *root_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_cb_editor_delete_entry(const char *path, long revision, void *parent_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
	ret = PyObject_CallMethod(self, "delete_entry", "sl", path, revision);
	if (ret == NULL)
		return py_svn_error();
    Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_add_directory(const char *path, void *parent_baton, const char *copyfrom_path, long copyfrom_revision, apr_pool_t *pool, void **child_baton)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
    *child_baton = NULL;
    if (copyfrom_path == NULL) {
        ret = PyObject_CallMethod(self, "add_directory", "s", path);
	} else {
        ret = PyObject_CallMethod(self, "add_directory", "ssl", path, copyfrom_path, copyfrom_revision);
	}
	if (ret == NULL)
		return py_svn_error();
    *child_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_cb_editor_open_directory(const char *path, void *parent_baton, long base_revision, apr_pool_t *pool, void **child_baton)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
    *child_baton = NULL;
    ret = PyObject_CallMethod(self, "open_directory", "sl", path, base_revision);
	if (ret == NULL)
		return py_svn_error();
    *child_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_cb_editor_change_prop(void *dir_baton, const char *name, const svn_string_t *value, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)dir_baton, *ret;
	if (value != NULL) {
		ret = PyObject_CallMethod(self, "change_prop", "sz#", name, value->data, value->len);
	} else {
		ret = PyObject_CallMethod(self, "change_prop", "sO", name, Py_None);
	}
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_close_directory(void *dir_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)dir_baton, *ret;
    ret = PyObject_CallMethod(self, "close", "");
    Py_DECREF(self);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_absent_directory(const char *path, void *parent_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
	ret = PyObject_CallMethod(self, "absent_directory", "s", path);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_add_file(const char *path, void *parent_baton, const char *copy_path, long copy_revision, apr_pool_t *file_pool, void **file_baton)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
    if (copy_path == NULL) {
		ret = PyObject_CallMethod(self, "add_file", "s", path);
	} else {
		ret = PyObject_CallMethod(self, "add_file", "ssl", path, copy_path, 
								  copy_revision);
	}
	if (ret == NULL)
		return py_svn_error();
    *file_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_cb_editor_open_file(const char *path, void *parent_baton, long base_revision, apr_pool_t *file_pool, void **file_baton)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
    ret = PyObject_CallMethod(self, "open_file", "sl", path, base_revision);
	if (ret == NULL)
		return py_svn_error();
    *file_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_txdelta_window_handler(svn_txdelta_window_t *window, void *baton)
{
	int i;
	PyObject *ops, *ret;
    PyObject *fn = (PyObject *)baton, *py_new_data, *py_window;
    if (window == NULL) {
        /* Signals all delta windows have been received */
        Py_DECREF(fn);
        return NULL;
	}
    if (fn == Py_None) {
        /* User doesn't care about deltas */
        return NULL;
	}
    ops = PyList_New(window->num_ops);
	for (i = 0; i < window->num_ops; i++) {
		PyList_SetItem(ops, i, Py_BuildValue("(iII)", window->ops[i].action_code, 
					window->ops[i].offset, 
					window->ops[i].length));
	}
	if (window->new_data != NULL && window->new_data->data != NULL) {
		py_new_data = PyString_FromStringAndSize(window->new_data->data, window->new_data->len);
	} else {
		py_new_data = Py_None;
	}
	py_window = Py_BuildValue("((LIIiOO))", window->sview_offset, window->sview_len, window->tview_len, 
								window->src_ops, ops, py_new_data);
	ret = PyObject_CallFunction(fn, "O", py_window);
	Py_DECREF(py_window);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_apply_textdelta(void *file_baton, const char *base_checksum, apr_pool_t *pool, svn_txdelta_window_handler_t *handler, void **handler_baton)
{
    PyObject *self = (PyObject *)file_baton, *ret;
    *handler_baton = NULL;
	ret = PyObject_CallMethod(self, "apply_textdelta", "z", base_checksum);
	if (ret == NULL)
		return py_svn_error();
    *handler_baton = (void *)ret;
    *handler = py_txdelta_window_handler;
    return NULL;
}

static svn_error_t *py_cb_editor_close_file(void *file_baton, 
										 const char *text_checksum, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)file_baton, *ret;
    if (text_checksum != NULL) {
		ret = PyObject_CallMethod(self, "close", "");
	} else {
		ret = PyObject_CallMethod(self, "close", "s", text_checksum);
	}
    Py_DECREF(self);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_absent_file(const char *path, void *parent_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
	ret = PyObject_CallMethod(self, "absent_file", "s", path);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_close_edit(void *edit_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)edit_baton, *ret;
	ret = PyObject_CallMethod(self, "close", "");
	Py_DECREF(self);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_abort_edit(void *edit_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)edit_baton, *ret;
	ret = PyObject_CallMethod(self, "abort", "");
	Py_DECREF(self);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static const svn_delta_editor_t py_editor = {
	.set_target_revision = py_cb_editor_set_target_revision,
	.open_root = py_cb_editor_open_root,
	.delete_entry = py_cb_editor_delete_entry,
	.add_directory = py_cb_editor_add_directory,
	.open_directory = py_cb_editor_open_directory,
	.change_dir_prop = py_cb_editor_change_prop,
	.close_directory = py_cb_editor_close_directory,
	.absent_directory = py_cb_editor_absent_directory,
	.add_file = py_cb_editor_add_file,
	.open_file = py_cb_editor_open_file,
	.apply_textdelta = py_cb_editor_apply_textdelta,
	.change_file_prop = py_cb_editor_change_prop,
	.close_file = py_cb_editor_close_file,
	.absent_file = py_cb_editor_absent_file,
	.close_edit = py_cb_editor_close_edit,
	.abort_edit = py_cb_editor_abort_edit
};

static svn_error_t *py_file_rev_handler(void *baton, const char *path, svn_revnum_t rev, apr_hash_t *rev_props, svn_txdelta_window_handler_t *delta_handler, void **delta_baton, apr_array_header_t *prop_diffs, apr_pool_t *pool)
{
    PyObject *fn = (PyObject *)baton, *ret, *py_rev_props;

	py_rev_props = prop_hash_to_dict(rev_props);
	if (py_rev_props == NULL)
		return py_svn_error();

	/* FIXME: delta handler */
	ret = PyObject_CallFunction(fn, "slO", path, rev, py_rev_props);
	Py_DECREF(py_rev_props);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

/** Connection to a remote Subversion repository. */
typedef struct {
	PyObject_HEAD
	svn_ra_session_t *ra;
    apr_pool_t *pool;
    const char *url;
    PyObject *progress_func;
	AuthObject *auth;
	bool busy;
} RemoteAccessObject;

static void ra_done_handler(void *_ra)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)_ra;

	ra->busy = false;
}

#define RUN_RA_WITH_POOL(pool, ra, cmd)  \
	if (!check_error((cmd))) { \
		apr_pool_destroy(pool); \
		ra->busy = false; \
		return NULL; \
	} \
	ra->busy = false;

static bool ra_check_busy(RemoteAccessObject *raobj)
{
	if (raobj->busy) {
		PyErr_SetString(busy_exc, "Remote access object already in use");
		return true;
	}
	raobj->busy = true;
	return false;
}

static svn_error_t *py_open_tmp_file(apr_file_t **fp, void *callback,
									 apr_pool_t *pool)
{
	RemoteAccessObject *self = (RemoteAccessObject *)callback;

	PyErr_SetString(PyExc_NotImplementedError, "open_tmp_file not wrapped yet");
	
	return py_svn_error(); /* FIXME */
}

static PyObject *ra_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
	char *kwnames[] = { "url", "progress_cb", "auth", "config", NULL };
	char *url;
	PyObject *progress_cb = Py_None;
	AuthObject *auth = (AuthObject *)Py_None;
	PyObject *config = Py_None;
	RemoteAccessObject *ret;
	apr_hash_t *config_hash;
	svn_ra_callbacks2_t *callbacks2;
	Py_ssize_t idx = 0;
	PyObject *key, *value;
	svn_auth_baton_t *auth_baton;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|OOO", kwnames, &url, &progress_cb, (PyObject **)&auth, &config))
		return NULL;

	ret = PyObject_New(RemoteAccessObject, &RemoteAccess_Type);
	if (ret == NULL)
		return NULL;

	if ((PyObject *)auth == Py_None) {
		auth_baton = NULL;
		ret->auth = NULL;
	} else {
		/* FIXME: check auth is an instance of Auth_Type */
		Py_INCREF(auth);
		ret->auth = auth;
		auth_baton = ret->auth->auth_baton;
	}

    ret->pool = Pool();
	if (ret->pool == NULL)
		return NULL;
	ret->url = apr_pstrdup(ret->pool, url);
	if (!check_error(svn_ra_create_callbacks(&callbacks2, ret->pool))) {
		apr_pool_destroy(ret->pool);
		PyObject_Del(ret);
		return NULL;
	}

	callbacks2->progress_func = py_progress_func;
	callbacks2->auth_baton = auth_baton;
	callbacks2->open_tmp_file = py_open_tmp_file;
	ret->progress_func = progress_cb;
	callbacks2->progress_baton = (void *)ret->progress_func;
	config_hash = apr_hash_make(ret->pool);
	if (PyDict_Check(config)) {
		while (PyDict_Next(config, &idx, &key, &value)) {
			apr_hash_set(config_hash, apr_pstrdup(ret->pool, PyString_AsString(key)), 
						 PyString_Size(key), apr_pstrdup(ret->pool, PyString_AsString(value)));
		}
	} else if (config != Py_None) {
		PyErr_SetString(PyExc_TypeError, "Expected dictionary for config");
		apr_pool_destroy(ret->pool);
		PyObject_Del(ret);
		return NULL;
	}
	if (!check_error(svn_ra_open2(&ret->ra, apr_pstrdup(ret->pool, url), 
								  callbacks2, ret, config_hash, ret->pool))) {
		apr_pool_destroy(ret->pool);
		PyObject_Del(ret);
		return NULL;
	}
	ret->busy = false;
	return (PyObject *)ret;
}

 /**
  * Obtain the globally unique identifier for this repository.
  */
static PyObject *ra_get_uuid(PyObject *self)
{
	const char *uuid;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	PyObject *ret;
    apr_pool_t *temp_pool;

	if (ra_check_busy(ra))
		return NULL;

    temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_uuid(ra->ra, &uuid, temp_pool));
	ret = PyString_FromString(uuid);
	apr_pool_destroy(temp_pool);
	return ret;
}

/** Switch to a different url. */
static PyObject *ra_reparent(PyObject *self, PyObject *args)
{
	char *url;
	apr_pool_t *temp_pool;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;

	if (!PyArg_ParseTuple(args, "s", &url))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	ra->url = svn_path_canonicalize(url, ra->pool);
	RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_reparent(ra->ra, ra->url, temp_pool));
	apr_pool_destroy(temp_pool);
	Py_RETURN_NONE;
}

/**
 * Obtain the number of the latest committed revision in the 
 * connected repository.
 */
static PyObject *ra_get_latest_revnum(PyObject *self)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	svn_revnum_t latest_revnum;
    apr_pool_t *temp_pool;
	if (ra_check_busy(ra))
		return NULL;

    temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	RUN_RA_WITH_POOL(temp_pool, ra,
				  svn_ra_get_latest_revnum(ra->ra, &latest_revnum, temp_pool));
    apr_pool_destroy(temp_pool);
    return PyInt_FromLong(latest_revnum);
}

static PyObject *ra_get_log(PyObject *self, PyObject *args, PyObject *kwargs)
{
	char *kwnames[] = { "callback", "paths", "start", "end", "limit",
		"discover_changed_paths", "strict_node_history", "revprops", NULL };
	PyObject *callback, *paths;
	svn_revnum_t start = 0, end = 0;
	int limit=0; 
	bool discover_changed_paths=false, strict_node_history=true;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	PyObject *revprops = Py_None;
    apr_pool_t *temp_pool;
	apr_array_header_t *apr_paths;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOll|ibbO:get_log", kwnames, 
						 &callback, &paths, &start, &end, &limit,
						 &discover_changed_paths, &strict_node_history,
						 &revprops))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

    temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	if (paths == Py_None) {
		/* FIXME: The subversion libraries don't behave as expected, 
		 * so tweak our own parameters a bit. */
		apr_paths = apr_array_make(temp_pool, 1, sizeof(char *));
		APR_ARRAY_PUSH(apr_paths, char *) = apr_pstrdup(temp_pool, "");
	} else if (!string_list_to_apr_array(temp_pool, paths, &apr_paths)) {
		apr_pool_destroy(temp_pool);
		return NULL;
	}
	RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_log(ra->ra, 
            apr_paths, start, end, limit,
            discover_changed_paths, strict_node_history, py_svn_log_wrapper, 
            callback, temp_pool));
    apr_pool_destroy(temp_pool);
	Py_RETURN_NONE;
}

/**
 * Obtain the URL of the root of this repository.
 */
static PyObject *ra_get_repos_root(PyObject *self)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	const char *root;
    apr_pool_t *temp_pool;
	
	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	RUN_RA_WITH_POOL(temp_pool, ra,
					  svn_ra_get_repos_root(ra->ra, &root, temp_pool));
	apr_pool_destroy(temp_pool);
	return PyString_FromString(root);
}

static PyObject *ra_do_update(PyObject *self, PyObject *args)
{
	svn_revnum_t revision_to_update_to;
	char *update_target; 
	bool recurse;
	PyObject *update_editor;
    const svn_ra_reporter2_t *reporter;
	void *report_baton;
	apr_pool_t *temp_pool;
	ReporterObject *ret;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;

	if (!PyArg_ParseTuple(args, "lsbO:do_update", &revision_to_update_to, &update_target, &recurse, &update_editor))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;

	Py_INCREF(update_editor);
	if (!check_error(svn_ra_do_update(ra->ra, &reporter, 
												  &report_baton, 
												  revision_to_update_to, 
												  update_target, recurse, 
												  &py_editor, update_editor, 
												  temp_pool))) {
		apr_pool_destroy(temp_pool);
		ra->busy = false;
		return NULL;
	}
	ret = PyObject_New(ReporterObject, &Reporter_Type);
	if (ret == NULL)
		return NULL;
	ret->reporter = reporter;
	ret->report_baton = report_baton;
	ret->pool = temp_pool;
	ret->done_cb = ra_done_handler;
	ret->done_baton = ra;
	return (PyObject *)ret;
}

static PyObject *ra_do_switch(PyObject *self, PyObject *args)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	svn_revnum_t revision_to_update_to;
	char *update_target; 
	bool recurse;
	char *switch_url; 
	PyObject *update_editor;
	const svn_ra_reporter2_t *reporter;
	void *report_baton;
	apr_pool_t *temp_pool;
	ReporterObject *ret;

	if (!PyArg_ParseTuple(args, "lsbsO:do_switch", &revision_to_update_to, &update_target, 
						  &recurse, &switch_url, &update_editor))
		return NULL;
	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	Py_INCREF(update_editor);
	if (!check_error(svn_ra_do_switch(
						ra->ra, &reporter, &report_baton, 
						revision_to_update_to, update_target, 
						recurse, switch_url, &py_editor, 
						update_editor, temp_pool))) {
		apr_pool_destroy(temp_pool);
		ra->busy = false;
		return NULL;
	}
	ret = PyObject_New(ReporterObject, &Reporter_Type);
	if (ret == NULL)
		return NULL;
	ret->reporter = reporter;
	ret->report_baton = report_baton;
	ret->pool = temp_pool;
	ret->done_cb = ra_done_handler;
	ret->done_baton = ra;
	return (PyObject *)ret;
}

static PyObject *ra_replay(PyObject *self, PyObject *args)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	apr_pool_t *temp_pool;
	svn_revnum_t revision, low_water_mark;
	PyObject *update_editor;
	bool send_deltas = true;

	if (!PyArg_ParseTuple(args, "llO|b", &revision, &low_water_mark, &update_editor, &send_deltas))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	Py_INCREF(update_editor);
    RUN_RA_WITH_POOL(temp_pool, ra,
					  svn_ra_replay(ra->ra, revision, low_water_mark,
									send_deltas, &py_editor, update_editor, 
									temp_pool));
	apr_pool_destroy(temp_pool);

	Py_RETURN_NONE;
}

static PyObject *ra_rev_proplist(PyObject *self, PyObject *args)
{
	apr_pool_t *temp_pool;
	apr_hash_t *props;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	svn_revnum_t rev;
	PyObject *py_props;
	if (!PyArg_ParseTuple(args, "l", &rev))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	RUN_RA_WITH_POOL(temp_pool, ra,
					  svn_ra_rev_proplist(ra->ra, rev, &props, temp_pool));
	py_props = prop_hash_to_dict(props);
	apr_pool_destroy(temp_pool);
	return py_props;
}

static PyObject *get_commit_editor(PyObject *self, PyObject *args, PyObject *kwargs)
{
	char *kwnames[] = { "revprops", "callback", "lock_tokens", "keep_locks", 
		NULL };
	PyObject *revprops, *commit_callback = Py_None, *lock_tokens = Py_None;
	bool keep_locks = false;
	apr_pool_t *pool;
	const svn_delta_editor_t *editor;
	void *edit_baton;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	apr_hash_t *hash_lock_tokens;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOb", kwnames, &revprops, &commit_callback, &lock_tokens, &keep_locks))
		return NULL;

	pool = Pool();
	if (pool == NULL)
		return NULL;
	if (lock_tokens == Py_None) {
		hash_lock_tokens = NULL;
	} else {
		Py_ssize_t idx = 0;
		PyObject *k, *v;
		hash_lock_tokens = apr_hash_make(pool);
		while (PyDict_Next(lock_tokens, &idx, &k, &v)) {
			apr_hash_set(hash_lock_tokens, PyString_AsString(k), 
						 PyString_Size(k), PyString_AsString(v));
		}
	}

	if (!PyDict_Check(revprops)) {
		apr_pool_destroy(pool);
		PyErr_SetString(PyExc_TypeError, "Expected dictionary with revision properties");
		return NULL;
	}

	if (ra_check_busy(ra))
		return NULL;

	if (!check_error(svn_ra_get_commit_editor2(ra->ra, &editor, 
		&edit_baton, 
		PyString_AsString(PyDict_GetItemString(revprops, SVN_PROP_REVISION_LOG)), py_commit_callback, 
		commit_callback, hash_lock_tokens, keep_locks, pool))) {
		apr_pool_destroy(pool);
		ra->busy = false;
		return NULL;
	}
	return new_editor_object(editor, edit_baton, pool, 
								  &Editor_Type, ra_done_handler, ra);
}

static PyObject *ra_change_rev_prop(PyObject *self, PyObject *args)
{<commit_msg>make sure we keep a handle open to the ra session.<commit_after>/* Copyright © 2008 <NAME> <<EMAIL>>
 * -*- coding: utf-8 -*-
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <stdbool.h>
#include <Python.h>
#include <apr_general.h>
#include <svn_types.h>
#include <svn_ra.h>
#include <svn_path.h>
#include <apr_file_io.h>

#include <structmember.h>

#include "editor.h"
#include "util.h"
#include "ra.h"

static PyObject *busy_exc;

PyAPI_DATA(PyTypeObject) Reporter_Type;
PyAPI_DATA(PyTypeObject) RemoteAccess_Type;
PyAPI_DATA(PyTypeObject) AuthProvider_Type;
PyAPI_DATA(PyTypeObject) TxDeltaWindowHandler_Type;

static svn_error_t *py_commit_callback(const svn_commit_info_t *commit_info, void *baton, apr_pool_t *pool)
{
	PyObject *fn = (PyObject *)baton, *ret;

	if (fn == Py_None)
		return NULL;

	ret = PyObject_CallFunction(fn, "izz", 
								commit_info->revision, commit_info->date, 
								commit_info->author);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
	return NULL;
}

static PyObject *pyify_lock(const svn_lock_t *lock)
{
    Py_RETURN_NONE; /* FIXME */
}

static svn_error_t *py_lock_func (void *baton, const char *path, int do_lock, 
                           const svn_lock_t *lock, svn_error_t *ra_err, 
                           apr_pool_t *pool)
{
    PyObject *py_ra_err = Py_None, *ret, *py_lock;
    if (ra_err != NULL) {
        py_ra_err = PyErr_NewSubversionException(ra_err);
	}
	py_lock = pyify_lock(lock);
    ret = PyObject_CallFunction((PyObject *)baton, "zbOO", path, do_lock, 
						  py_lock, py_ra_err);
	Py_DECREF(py_lock);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
	return NULL;
}

static void py_progress_func(apr_off_t progress, apr_off_t total, void *baton, apr_pool_t *pool)
{
    PyObject *fn = (PyObject *)baton, *ret;
    if (fn == Py_None) {
        return;
	}
	ret = PyObject_CallFunction(fn, "ll", progress, total);
	/* TODO: What to do with exceptions raised here ? */
	if (ret == NULL)
		return;
	Py_DECREF(ret);
}


typedef struct {
	PyObject_HEAD
    const svn_ra_reporter2_t *reporter;
    void *report_baton;
    apr_pool_t *pool;
	void (*done_cb)(void *baton);
	void *done_baton;
} ReporterObject;

static PyObject *reporter_set_path(PyObject *self, PyObject *args)
{
	char *path; 
	svn_revnum_t revision; 
	bool start_empty; 
	char *lock_token = NULL;
	ReporterObject *reporter = (ReporterObject *)self;

	if (!PyArg_ParseTuple(args, "slb|z", &path, &revision, &start_empty, 
						  &lock_token))
		return NULL;

    if (!check_error(reporter->reporter->set_path(reporter->report_baton, 
												  path, revision, start_empty, 
					 lock_token, reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyObject *reporter_delete_path(PyObject *self, PyObject *args)
{
	ReporterObject *reporter = (ReporterObject *)self;
	char *path;
	if (!PyArg_ParseTuple(args, "s", &path))
		return NULL;

	if (!check_error(reporter->reporter->delete_path(reporter->report_baton, 
													path, reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyObject *reporter_link_path(PyObject *self, PyObject *args)
{
	char *path, *url;
	svn_revnum_t revision;
	bool start_empty;
	char *lock_token = NULL;
	ReporterObject *reporter = (ReporterObject *)self;

	if (!PyArg_ParseTuple(args, "sslb|z", &path, &url, &revision, &start_empty, &lock_token))
		return NULL;

	if (!check_error(reporter->reporter->link_path(reporter->report_baton, path, url, 
				revision, start_empty, lock_token, reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyObject *reporter_finish(PyObject *self)
{
	ReporterObject *reporter = (ReporterObject *)self;

	if (reporter->done_cb != NULL)
		reporter->done_cb(reporter->done_baton);

	if (!check_error(reporter->reporter->finish_report(reporter->report_baton, 
													  reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyObject *reporter_abort(PyObject *self)
{
	ReporterObject *reporter = (ReporterObject *)self;
	
	if (reporter->done_cb != NULL)
		reporter->done_cb(reporter->done_baton);

	if (!check_error(reporter->reporter->abort_report(reporter->report_baton, 
													 reporter->pool)))
		return NULL;

	Py_RETURN_NONE;
}

static PyMethodDef reporter_methods[] = {
	{ "abort", (PyCFunction)reporter_abort, METH_NOARGS, NULL },
	{ "finish", (PyCFunction)reporter_finish, METH_NOARGS, NULL },
	{ "link_path", (PyCFunction)reporter_link_path, METH_VARARGS, NULL },
	{ "set_path", (PyCFunction)reporter_set_path, METH_VARARGS, NULL },
	{ "delete_path", (PyCFunction)reporter_delete_path, METH_VARARGS, NULL },
	{ NULL, }
};

static void reporter_dealloc(PyObject *self)
{
	ReporterObject *reporter = (ReporterObject *)self;
	/* FIXME: Warn the user if abort_report/finish_report wasn't called? */
	apr_pool_destroy(reporter->pool);
	PyObject_Del(self);
}

PyTypeObject Reporter_Type = {
	PyObject_HEAD_INIT(NULL) 0,
	.tp_name = "ra.Reporter",
	.tp_basicsize = sizeof(ReporterObject),
	.tp_methods = reporter_methods,
	.tp_dealloc = reporter_dealloc,
};

/**
 * Get libsvn_ra version information.
 *
 * :return: tuple with major, minor, patch version number and tag.
 */
static PyObject *version(PyObject *self)
{
    const svn_version_t *ver = svn_ra_version();
    return Py_BuildValue("(iiis)", ver->major, ver->minor, 
						 ver->patch, ver->tag);
}

static svn_error_t *py_cb_editor_set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)edit_baton, *ret;

	ret = PyObject_CallMethod(self, "set_target_revision", "l", target_revision);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **root_baton)
{
    PyObject *self = (PyObject *)edit_baton, *ret;
    *root_baton = NULL;
    ret = PyObject_CallMethod(self, "open_root", "l", base_revision);
	if (ret == NULL)
		return py_svn_error();
    *root_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_cb_editor_delete_entry(const char *path, long revision, void *parent_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
	ret = PyObject_CallMethod(self, "delete_entry", "sl", path, revision);
	if (ret == NULL)
		return py_svn_error();
    Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_add_directory(const char *path, void *parent_baton, const char *copyfrom_path, long copyfrom_revision, apr_pool_t *pool, void **child_baton)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
    *child_baton = NULL;
    if (copyfrom_path == NULL) {
        ret = PyObject_CallMethod(self, "add_directory", "s", path);
	} else {
        ret = PyObject_CallMethod(self, "add_directory", "ssl", path, copyfrom_path, copyfrom_revision);
	}
	if (ret == NULL)
		return py_svn_error();
    *child_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_cb_editor_open_directory(const char *path, void *parent_baton, long base_revision, apr_pool_t *pool, void **child_baton)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
    *child_baton = NULL;
    ret = PyObject_CallMethod(self, "open_directory", "sl", path, base_revision);
	if (ret == NULL)
		return py_svn_error();
    *child_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_cb_editor_change_prop(void *dir_baton, const char *name, const svn_string_t *value, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)dir_baton, *ret;
	if (value != NULL) {
		ret = PyObject_CallMethod(self, "change_prop", "sz#", name, value->data, value->len);
	} else {
		ret = PyObject_CallMethod(self, "change_prop", "sO", name, Py_None);
	}
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_close_directory(void *dir_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)dir_baton, *ret;
    ret = PyObject_CallMethod(self, "close", "");
    Py_DECREF(self);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_absent_directory(const char *path, void *parent_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
	ret = PyObject_CallMethod(self, "absent_directory", "s", path);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_add_file(const char *path, void *parent_baton, const char *copy_path, long copy_revision, apr_pool_t *file_pool, void **file_baton)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
    if (copy_path == NULL) {
		ret = PyObject_CallMethod(self, "add_file", "s", path);
	} else {
		ret = PyObject_CallMethod(self, "add_file", "ssl", path, copy_path, 
								  copy_revision);
	}
	if (ret == NULL)
		return py_svn_error();
    *file_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_cb_editor_open_file(const char *path, void *parent_baton, long base_revision, apr_pool_t *file_pool, void **file_baton)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
    ret = PyObject_CallMethod(self, "open_file", "sl", path, base_revision);
	if (ret == NULL)
		return py_svn_error();
    *file_baton = (void *)ret;
    return NULL;
}

static svn_error_t *py_txdelta_window_handler(svn_txdelta_window_t *window, void *baton)
{
	int i;
	PyObject *ops, *ret;
    PyObject *fn = (PyObject *)baton, *py_new_data, *py_window;
    if (window == NULL) {
        /* Signals all delta windows have been received */
        Py_DECREF(fn);
        return NULL;
	}
    if (fn == Py_None) {
        /* User doesn't care about deltas */
        return NULL;
	}
    ops = PyList_New(window->num_ops);
	for (i = 0; i < window->num_ops; i++) {
		PyList_SetItem(ops, i, Py_BuildValue("(iII)", window->ops[i].action_code, 
					window->ops[i].offset, 
					window->ops[i].length));
	}
	if (window->new_data != NULL && window->new_data->data != NULL) {
		py_new_data = PyString_FromStringAndSize(window->new_data->data, window->new_data->len);
	} else {
		py_new_data = Py_None;
	}
	py_window = Py_BuildValue("((LIIiOO))", window->sview_offset, window->sview_len, window->tview_len, 
								window->src_ops, ops, py_new_data);
	ret = PyObject_CallFunction(fn, "O", py_window);
	Py_DECREF(py_window);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_apply_textdelta(void *file_baton, const char *base_checksum, apr_pool_t *pool, svn_txdelta_window_handler_t *handler, void **handler_baton)
{
    PyObject *self = (PyObject *)file_baton, *ret;
    *handler_baton = NULL;
	ret = PyObject_CallMethod(self, "apply_textdelta", "z", base_checksum);
	if (ret == NULL)
		return py_svn_error();
    *handler_baton = (void *)ret;
    *handler = py_txdelta_window_handler;
    return NULL;
}

static svn_error_t *py_cb_editor_close_file(void *file_baton, 
										 const char *text_checksum, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)file_baton, *ret;
    if (text_checksum != NULL) {
		ret = PyObject_CallMethod(self, "close", "");
	} else {
		ret = PyObject_CallMethod(self, "close", "s", text_checksum);
	}
    Py_DECREF(self);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_absent_file(const char *path, void *parent_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)parent_baton, *ret;
	ret = PyObject_CallMethod(self, "absent_file", "s", path);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_close_edit(void *edit_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)edit_baton, *ret;
	ret = PyObject_CallMethod(self, "close", "");
	Py_DECREF(self);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static svn_error_t *py_cb_editor_abort_edit(void *edit_baton, apr_pool_t *pool)
{
    PyObject *self = (PyObject *)edit_baton, *ret;
	ret = PyObject_CallMethod(self, "abort", "");
	Py_DECREF(self);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

static const svn_delta_editor_t py_editor = {
	.set_target_revision = py_cb_editor_set_target_revision,
	.open_root = py_cb_editor_open_root,
	.delete_entry = py_cb_editor_delete_entry,
	.add_directory = py_cb_editor_add_directory,
	.open_directory = py_cb_editor_open_directory,
	.change_dir_prop = py_cb_editor_change_prop,
	.close_directory = py_cb_editor_close_directory,
	.absent_directory = py_cb_editor_absent_directory,
	.add_file = py_cb_editor_add_file,
	.open_file = py_cb_editor_open_file,
	.apply_textdelta = py_cb_editor_apply_textdelta,
	.change_file_prop = py_cb_editor_change_prop,
	.close_file = py_cb_editor_close_file,
	.absent_file = py_cb_editor_absent_file,
	.close_edit = py_cb_editor_close_edit,
	.abort_edit = py_cb_editor_abort_edit
};

static svn_error_t *py_file_rev_handler(void *baton, const char *path, svn_revnum_t rev, apr_hash_t *rev_props, svn_txdelta_window_handler_t *delta_handler, void **delta_baton, apr_array_header_t *prop_diffs, apr_pool_t *pool)
{
    PyObject *fn = (PyObject *)baton, *ret, *py_rev_props;

	py_rev_props = prop_hash_to_dict(rev_props);
	if (py_rev_props == NULL)
		return py_svn_error();

	/* FIXME: delta handler */
	ret = PyObject_CallFunction(fn, "slO", path, rev, py_rev_props);
	Py_DECREF(py_rev_props);
	if (ret == NULL)
		return py_svn_error();
	Py_DECREF(ret);
    return NULL;
}

/** Connection to a remote Subversion repository. */
typedef struct {
	PyObject_HEAD
	svn_ra_session_t *ra;
    apr_pool_t *pool;
    const char *url;
    PyObject *progress_func;
	AuthObject *auth;
	bool busy;
} RemoteAccessObject;

static void ra_done_handler(void *_ra)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)_ra;

	ra->busy = false;

	Py_XDECREF(ra);
}

#define RUN_RA_WITH_POOL(pool, ra, cmd)  \
	if (!check_error((cmd))) { \
		apr_pool_destroy(pool); \
		ra->busy = false; \
		return NULL; \
	} \
	ra->busy = false;

static bool ra_check_busy(RemoteAccessObject *raobj)
{
	if (raobj->busy) {
		PyErr_SetString(busy_exc, "Remote access object already in use");
		return true;
	}
	raobj->busy = true;
	return false;
}

static svn_error_t *py_open_tmp_file(apr_file_t **fp, void *callback,
									 apr_pool_t *pool)
{
	RemoteAccessObject *self = (RemoteAccessObject *)callback;

	PyErr_SetString(PyExc_NotImplementedError, "open_tmp_file not wrapped yet");
	
	return py_svn_error(); /* FIXME */
}

static PyObject *ra_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
	char *kwnames[] = { "url", "progress_cb", "auth", "config", NULL };
	char *url;
	PyObject *progress_cb = Py_None;
	AuthObject *auth = (AuthObject *)Py_None;
	PyObject *config = Py_None;
	RemoteAccessObject *ret;
	apr_hash_t *config_hash;
	svn_ra_callbacks2_t *callbacks2;
	Py_ssize_t idx = 0;
	PyObject *key, *value;
	svn_auth_baton_t *auth_baton;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|OOO", kwnames, &url, &progress_cb, (PyObject **)&auth, &config))
		return NULL;

	ret = PyObject_New(RemoteAccessObject, &RemoteAccess_Type);
	if (ret == NULL)
		return NULL;

	if ((PyObject *)auth == Py_None) {
		auth_baton = NULL;
		ret->auth = NULL;
	} else {
		/* FIXME: check auth is an instance of Auth_Type */
		Py_INCREF(auth);
		ret->auth = auth;
		auth_baton = ret->auth->auth_baton;
	}

    ret->pool = Pool();
	if (ret->pool == NULL)
		return NULL;
	ret->url = apr_pstrdup(ret->pool, url);
	if (!check_error(svn_ra_create_callbacks(&callbacks2, ret->pool))) {
		apr_pool_destroy(ret->pool);
		PyObject_Del(ret);
		return NULL;
	}

	callbacks2->progress_func = py_progress_func;
	callbacks2->auth_baton = auth_baton;
	callbacks2->open_tmp_file = py_open_tmp_file;
	ret->progress_func = progress_cb;
	callbacks2->progress_baton = (void *)ret->progress_func;
	config_hash = apr_hash_make(ret->pool);
	if (PyDict_Check(config)) {
		while (PyDict_Next(config, &idx, &key, &value)) {
			apr_hash_set(config_hash, apr_pstrdup(ret->pool, PyString_AsString(key)), 
						 PyString_Size(key), apr_pstrdup(ret->pool, PyString_AsString(value)));
		}
	} else if (config != Py_None) {
		PyErr_SetString(PyExc_TypeError, "Expected dictionary for config");
		apr_pool_destroy(ret->pool);
		PyObject_Del(ret);
		return NULL;
	}
	if (!check_error(svn_ra_open2(&ret->ra, apr_pstrdup(ret->pool, url), 
								  callbacks2, ret, config_hash, ret->pool))) {
		apr_pool_destroy(ret->pool);
		PyObject_Del(ret);
		return NULL;
	}
	ret->busy = false;
	return (PyObject *)ret;
}

 /**
  * Obtain the globally unique identifier for this repository.
  */
static PyObject *ra_get_uuid(PyObject *self)
{
	const char *uuid;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	PyObject *ret;
    apr_pool_t *temp_pool;

	if (ra_check_busy(ra))
		return NULL;

    temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_uuid(ra->ra, &uuid, temp_pool));
	ret = PyString_FromString(uuid);
	apr_pool_destroy(temp_pool);
	return ret;
}

/** Switch to a different url. */
static PyObject *ra_reparent(PyObject *self, PyObject *args)
{
	char *url;
	apr_pool_t *temp_pool;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;

	if (!PyArg_ParseTuple(args, "s", &url))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	ra->url = svn_path_canonicalize(url, ra->pool);
	RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_reparent(ra->ra, ra->url, temp_pool));
	apr_pool_destroy(temp_pool);
	Py_RETURN_NONE;
}

/**
 * Obtain the number of the latest committed revision in the 
 * connected repository.
 */
static PyObject *ra_get_latest_revnum(PyObject *self)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	svn_revnum_t latest_revnum;
    apr_pool_t *temp_pool;
	if (ra_check_busy(ra))
		return NULL;

    temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	RUN_RA_WITH_POOL(temp_pool, ra,
				  svn_ra_get_latest_revnum(ra->ra, &latest_revnum, temp_pool));
    apr_pool_destroy(temp_pool);
    return PyInt_FromLong(latest_revnum);
}

static PyObject *ra_get_log(PyObject *self, PyObject *args, PyObject *kwargs)
{
	char *kwnames[] = { "callback", "paths", "start", "end", "limit",
		"discover_changed_paths", "strict_node_history", "revprops", NULL };
	PyObject *callback, *paths;
	svn_revnum_t start = 0, end = 0;
	int limit=0; 
	bool discover_changed_paths=false, strict_node_history=true;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	PyObject *revprops = Py_None;
    apr_pool_t *temp_pool;
	apr_array_header_t *apr_paths;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOll|ibbO:get_log", kwnames, 
						 &callback, &paths, &start, &end, &limit,
						 &discover_changed_paths, &strict_node_history,
						 &revprops))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

    temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	if (paths == Py_None) {
		/* FIXME: The subversion libraries don't behave as expected, 
		 * so tweak our own parameters a bit. */
		apr_paths = apr_array_make(temp_pool, 1, sizeof(char *));
		APR_ARRAY_PUSH(apr_paths, char *) = apr_pstrdup(temp_pool, "");
	} else if (!string_list_to_apr_array(temp_pool, paths, &apr_paths)) {
		apr_pool_destroy(temp_pool);
		return NULL;
	}
	RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_log(ra->ra, 
            apr_paths, start, end, limit,
            discover_changed_paths, strict_node_history, py_svn_log_wrapper, 
            callback, temp_pool));
    apr_pool_destroy(temp_pool);
	Py_RETURN_NONE;
}

/**
 * Obtain the URL of the root of this repository.
 */
static PyObject *ra_get_repos_root(PyObject *self)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	const char *root;
    apr_pool_t *temp_pool;
	
	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	RUN_RA_WITH_POOL(temp_pool, ra,
					  svn_ra_get_repos_root(ra->ra, &root, temp_pool));
	apr_pool_destroy(temp_pool);
	return PyString_FromString(root);
}

static PyObject *ra_do_update(PyObject *self, PyObject *args)
{
	svn_revnum_t revision_to_update_to;
	char *update_target; 
	bool recurse;
	PyObject *update_editor;
    const svn_ra_reporter2_t *reporter;
	void *report_baton;
	apr_pool_t *temp_pool;
	ReporterObject *ret;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;

	if (!PyArg_ParseTuple(args, "lsbO:do_update", &revision_to_update_to, &update_target, &recurse, &update_editor))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;

	Py_INCREF(update_editor);
	if (!check_error(svn_ra_do_update(ra->ra, &reporter, 
												  &report_baton, 
												  revision_to_update_to, 
												  update_target, recurse, 
												  &py_editor, update_editor, 
												  temp_pool))) {
		apr_pool_destroy(temp_pool);
		ra->busy = false;
		return NULL;
	}
	ret = PyObject_New(ReporterObject, &Reporter_Type);
	if (ret == NULL)
		return NULL;
	ret->reporter = reporter;
	ret->report_baton = report_baton;
	ret->pool = temp_pool;
	Py_INCREF(ra);
	ret->done_cb = ra_done_handler;
	ret->done_baton = ra;
	return (PyObject *)ret;
}

static PyObject *ra_do_switch(PyObject *self, PyObject *args)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	svn_revnum_t revision_to_update_to;
	char *update_target; 
	bool recurse;
	char *switch_url; 
	PyObject *update_editor;
	const svn_ra_reporter2_t *reporter;
	void *report_baton;
	apr_pool_t *temp_pool;
	ReporterObject *ret;

	if (!PyArg_ParseTuple(args, "lsbsO:do_switch", &revision_to_update_to, &update_target, 
						  &recurse, &switch_url, &update_editor))
		return NULL;
	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	Py_INCREF(update_editor);
	if (!check_error(svn_ra_do_switch(
						ra->ra, &reporter, &report_baton, 
						revision_to_update_to, update_target, 
						recurse, switch_url, &py_editor, 
						update_editor, temp_pool))) {
		apr_pool_destroy(temp_pool);
		ra->busy = false;
		return NULL;
	}
	ret = PyObject_New(ReporterObject, &Reporter_Type);
	if (ret == NULL)
		return NULL;
	ret->reporter = reporter;
	ret->report_baton = report_baton;
	ret->pool = temp_pool;
	Py_INCREF(ra);
	ret->done_cb = ra_done_handler;
	ret->done_baton = ra;
	return (PyObject *)ret;
}

static PyObject *ra_replay(PyObject *self, PyObject *args)
{
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	apr_pool_t *temp_pool;
	svn_revnum_t revision, low_water_mark;
	PyObject *update_editor;
	bool send_deltas = true;

	if (!PyArg_ParseTuple(args, "llO|b", &revision, &low_water_mark, &update_editor, &send_deltas))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	Py_INCREF(update_editor);
    RUN_RA_WITH_POOL(temp_pool, ra,
					  svn_ra_replay(ra->ra, revision, low_water_mark,
									send_deltas, &py_editor, update_editor, 
									temp_pool));
	apr_pool_destroy(temp_pool);

	Py_RETURN_NONE;
}

static PyObject *ra_rev_proplist(PyObject *self, PyObject *args)
{
	apr_pool_t *temp_pool;
	apr_hash_t *props;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	svn_revnum_t rev;
	PyObject *py_props;
	if (!PyArg_ParseTuple(args, "l", &rev))
		return NULL;

	if (ra_check_busy(ra))
		return NULL;

	temp_pool = Pool();
	if (temp_pool == NULL)
		return NULL;
	RUN_RA_WITH_POOL(temp_pool, ra,
					  svn_ra_rev_proplist(ra->ra, rev, &props, temp_pool));
	py_props = prop_hash_to_dict(props);
	apr_pool_destroy(temp_pool);
	return py_props;
}

static PyObject *get_commit_editor(PyObject *self, PyObject *args, PyObject *kwargs)
{
	char *kwnames[] = { "revprops", "callback", "lock_tokens", "keep_locks", 
		NULL };
	PyObject *revprops, *commit_callback = Py_None, *lock_tokens = Py_None;
	bool keep_locks = false;
	apr_pool_t *pool;
	const svn_delta_editor_t *editor;
	void *edit_baton;
	RemoteAccessObject *ra = (RemoteAccessObject *)self;
	apr_hash_t *hash_lock_tokens;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOb", kwnames, &revprops, &commit_callback, &lock_tokens, &keep_locks))
		return NULL;

	pool = Pool();
	if (pool == NULL)
		return NULL;
	if (lock_tokens == Py_None) {
		hash_lock_tokens = NULL;
	} else {
		Py_ssize_t idx = 0;
		PyObject *k, *v;
		hash_lock_tokens = apr_hash_make(pool);
		while (PyDict_Next(lock_tokens, &idx, &k, &v)) {
			apr_hash_set(hash_lock_tokens, PyString_AsString(k), 
						 PyString_Size(k), PyString_AsString(v));
		}
	}

	if (!PyDict_Check(revprops)) {
		apr_pool_destroy(pool);
		PyErr_SetString(PyExc_TypeError, "Expected dictionary with revision properties");
		return NULL;
	}

	if (ra_check_busy(ra))
		return NULL;

	if (!check_error(svn_ra_get_commit_editor2(ra->ra, &editor, 
		&edit_baton, 
		PyString_AsString(PyDict_GetItemString(revprops, SVN_PROP_REVISION_LOG)), py_commit_callback, 
		commit_callback, hash_lock_tokens, keep_locks, pool))) {
		apr_pool_destroy(pool);
		ra->busy = false;
		return NULL;
	}

	Py_INCREF(ra);
	return new_editor_object(editor, edit_baton, pool, 
								  &Editor_Type, ra_done_handler, ra);
}

static PyObject *ra_change_rev_prop(PyObject *self, PyObject *args)
{
{'fraction_non_alphanumeric': 0.12502904500038728, 'fraction_numerical': 0.0015877933545039114, 'mean_word_length': 4.823748308525034, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 241, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2150800', 'n_tokens_mistral': 20424, 'n_tokens_neox': 19813, 'n_words': 4218}