summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_ra_svn/editorp.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_ra_svn/editorp.c')
-rw-r--r--subversion/libsvn_ra_svn/editorp.c1044
1 files changed, 1044 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_svn/editorp.c b/subversion/libsvn_ra_svn/editorp.c
new file mode 100644
index 0000000..cc1d8ab
--- /dev/null
+++ b/subversion/libsvn_ra_svn/editorp.c
@@ -0,0 +1,1044 @@
+/*
+ * editorp.c : Driving and consuming an editor across an svn connection
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_ra_svn.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_private_config.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_editor.h"
+
+#include "ra_svn.h"
+
+/*
+ * Both the client and server in the svn protocol need to drive and
+ * consume editors. For a commit, the client drives and the server
+ * consumes; for an update/switch/status/diff, the server drives and
+ * the client consumes. This file provides a generic framework for
+ * marshalling and unmarshalling editor operations over an svn
+ * connection; both ends are useful for both server and client.
+ */
+
+typedef struct ra_svn_edit_baton_t {
+ svn_ra_svn_conn_t *conn;
+ svn_ra_svn_edit_callback callback; /* Called on successful completion. */
+ void *callback_baton;
+ int next_token;
+ svn_boolean_t got_status;
+} ra_svn_edit_baton_t;
+
+/* Works for both directories and files. */
+typedef struct ra_svn_baton_t {
+ svn_ra_svn_conn_t *conn;
+ apr_pool_t *pool;
+ ra_svn_edit_baton_t *eb;
+ const char *token;
+} ra_svn_baton_t;
+
+typedef struct ra_svn_driver_state_t {
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_hash_t *tokens;
+ svn_boolean_t *aborted;
+ svn_boolean_t done;
+ apr_pool_t *pool;
+ apr_pool_t *file_pool;
+ int file_refs;
+ svn_boolean_t for_replay;
+} ra_svn_driver_state_t;
+
+/* Works for both directories and files; however, the pool handling is
+ different for files. To save space during commits (where file
+ batons generally last until the end of the commit), token entries
+ for files are all created in a single reference-counted pool (the
+ file_pool member of the driver state structure), which is cleared
+ at close_file time when the reference count hits zero. So the pool
+ field in this structure is vestigial for files, and we use it for a
+ different purpose instead: at apply-textdelta time, we set it to a
+ subpool of the file pool, which is destroyed in textdelta-end. */
+typedef struct ra_svn_token_entry_t {
+ const char *token;
+ void *baton;
+ svn_boolean_t is_file;
+ svn_stream_t *dstream; /* svndiff stream for apply_textdelta */
+ apr_pool_t *pool;
+} ra_svn_token_entry_t;
+
+/* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */
+
+static const char *make_token(char type, ra_svn_edit_baton_t *eb,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "%c%d", type, eb->next_token++);
+}
+
+static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ ra_svn_edit_baton_t *eb,
+ const char *token)
+{
+ ra_svn_baton_t *b;
+
+ b = apr_palloc(pool, sizeof(*b));
+ b->conn = conn;
+ b->pool = pool;
+ b->eb = eb;
+ b->token = token;
+ return b;
+}
+
+/* Check for an early error status report from the consumer. If we
+ * get one, abort the edit and return the error. */
+static svn_error_t *
+check_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(!eb->got_status);
+
+ /* reset TX counter */
+ eb->conn->written_since_error_check = 0;
+
+ /* if we weren't asked to always check, wait for at least the next TX */
+ eb->conn->may_check_for_error = eb->conn->error_check_interval == 0;
+
+ /* any incoming data? */
+ if (svn_ra_svn__input_waiting(eb->conn, pool))
+ {
+ eb->got_status = TRUE;
+ SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
+ /* We shouldn't get here if the consumer is doing its job. */
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Successful edit status returned too soon"));
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
+{
+ return eb->conn->may_check_for_error
+ ? check_for_error_internal(eb, pool)
+ : SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ ra_svn_edit_baton_t *eb = edit_baton;
+
+ SVN_ERR(check_for_error(eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
+ apr_pool_t *pool, void **root_baton)
+{
+ ra_svn_edit_baton_t *eb = edit_baton;
+ const char *token = make_token('d', eb, pool);
+
+ SVN_ERR(check_for_error(eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_open_root(eb->conn, pool, rev, token));
+ *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
+ void *parent_baton, apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = parent_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_delete_entry(b->conn, pool,
+ path, rev, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_rev,
+ apr_pool_t *pool, void **child_baton)
+{
+ ra_svn_baton_t *b = parent_baton;
+ const char *token = make_token('d', b->eb, pool);
+
+ SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
+ || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_add_dir(b->conn, pool, path, b->token,
+ token, copy_path, copy_rev));
+ *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
+ svn_revnum_t rev, apr_pool_t *pool,
+ void **child_baton)
+{
+ ra_svn_baton_t *b = parent_baton;
+ const char *token = make_token('d', b->eb, pool);
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_open_dir(b->conn, pool, path, b->token,
+ token, rev));
+ *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = dir_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_change_dir_prop(b->conn, pool, b->token,
+ name, value));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = dir_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_absent_dir(const char *path,
+ void *parent_baton, apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = parent_baton;
+
+ /* Avoid sending an unknown command if the other end doesn't support
+ absent-dir. */
+ if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_absent_dir(b->conn, pool, path, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_add_file(const char *path,
+ void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ ra_svn_baton_t *b = parent_baton;
+ const char *token = make_token('c', b->eb, pool);
+
+ SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
+ || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_add_file(b->conn, pool, path, b->token,
+ token, copy_path, copy_rev));
+ *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ ra_svn_baton_t *b = parent_baton;
+ const char *token = make_token('c', b->eb, pool);
+
+ SVN_ERR(check_for_error(b->eb, b->pool));
+ SVN_ERR(svn_ra_svn__write_cmd_open_file(b->conn, pool, path, b->token,
+ token, rev));
+ *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
+ apr_size_t *len)
+{
+ ra_svn_baton_t *b = baton;
+ svn_string_t str;
+
+ SVN_ERR(check_for_error(b->eb, b->pool));
+ str.data = data;
+ str.len = *len;
+ return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool,
+ b->token, &str);
+}
+
+static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
+{
+ ra_svn_baton_t *b = baton;
+
+ SVN_ERR(check_for_error(b->eb, b->pool));
+ SVN_ERR(svn_ra_svn__write_cmd_textdelta_end(b->conn, b->pool, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *wh,
+ void **wh_baton)
+{
+ ra_svn_baton_t *b = file_baton;
+ svn_stream_t *diff_stream;
+
+ /* Tell the other side we're starting a text delta. */
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_apply_textdelta(b->conn, pool, b->token,
+ base_checksum));
+
+ /* Transform the window stream to an svndiff stream. Reuse the
+ * file baton for the stream handler, since it has all the
+ * needed information. */
+ diff_stream = svn_stream_create(b, pool);
+ svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
+ svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
+
+ /* If the connection does not support SVNDIFF1 or if we don't want to use
+ * compression, use the non-compressing "version 0" implementation */
+ if ( svn_ra_svn_compression_level(b->conn) > 0
+ && svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_SVNDIFF1))
+ svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 1,
+ b->conn->compression_level, pool);
+ else
+ svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 0,
+ b->conn->compression_level, pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = file_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_change_file_prop(b->conn, pool,
+ b->token, name, value));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = file_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_close_file(b->conn, pool,
+ b->token, text_checksum));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_absent_file(const char *path,
+ void *parent_baton, apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = parent_baton;
+
+ /* Avoid sending an unknown command if the other end doesn't support
+ absent-file. */
+ if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_absent_file(b->conn, pool, path, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
+{
+ ra_svn_edit_baton_t *eb = edit_baton;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(!eb->got_status);
+ eb->got_status = TRUE;
+ SVN_ERR(svn_ra_svn__write_cmd_close_edit(eb->conn, pool));
+ err = svn_ra_svn__read_cmd_response(eb->conn, pool, "");
+ if (err)
+ {
+ svn_error_clear(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
+ return err;
+ }
+ if (eb->callback)
+ SVN_ERR(eb->callback(eb->callback_baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
+{
+ ra_svn_edit_baton_t *eb = edit_baton;
+
+ if (eb->got_status)
+ return SVN_NO_ERROR;
+ SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
+ return SVN_NO_ERROR;
+}
+
+void svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
+ void **edit_baton, svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_ra_svn_edit_callback callback,
+ void *callback_baton)
+{
+ svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool);
+ ra_svn_edit_baton_t *eb;
+
+ eb = apr_palloc(pool, sizeof(*eb));
+ eb->conn = conn;
+ eb->callback = callback;
+ eb->callback_baton = callback_baton;
+ eb->next_token = 0;
+ eb->got_status = FALSE;
+
+ ra_svn_editor->set_target_revision = ra_svn_target_rev;
+ ra_svn_editor->open_root = ra_svn_open_root;
+ ra_svn_editor->delete_entry = ra_svn_delete_entry;
+ ra_svn_editor->add_directory = ra_svn_add_dir;
+ ra_svn_editor->open_directory = ra_svn_open_dir;
+ ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
+ ra_svn_editor->close_directory = ra_svn_close_dir;
+ ra_svn_editor->absent_directory = ra_svn_absent_dir;
+ ra_svn_editor->add_file = ra_svn_add_file;
+ ra_svn_editor->open_file = ra_svn_open_file;
+ ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
+ ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
+ ra_svn_editor->close_file = ra_svn_close_file;
+ ra_svn_editor->absent_file = ra_svn_absent_file;
+ ra_svn_editor->close_edit = ra_svn_close_edit;
+ ra_svn_editor->abort_edit = ra_svn_abort_edit;
+
+ *editor = ra_svn_editor;
+ *edit_baton = eb;
+
+ svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor,
+ *edit_baton, NULL, NULL,
+ conn->shim_callbacks,
+ pool, pool));
+}
+
+/* --- DRIVING AN EDITOR --- */
+
+/* Store a token entry. The token string will be copied into pool. */
+static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
+ void *baton, const char *token,
+ svn_boolean_t is_file,
+ apr_pool_t *pool)
+{
+ ra_svn_token_entry_t *entry;
+
+ entry = apr_palloc(pool, sizeof(*entry));
+ entry->token = apr_pstrdup(pool, token);
+ entry->baton = baton;
+ entry->is_file = is_file;
+ entry->dstream = NULL;
+ entry->pool = pool;
+ svn_hash_sets(ds->tokens, entry->token, entry);
+ return entry;
+}
+
+static svn_error_t *lookup_token(ra_svn_driver_state_t *ds, const char *token,
+ svn_boolean_t is_file,
+ ra_svn_token_entry_t **entry)
+{
+ *entry = svn_hash_gets(ds->tokens, token);
+ if (!*entry || (*entry)->is_file != is_file)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Invalid file or dir token during edit"));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ svn_revnum_t rev;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
+ SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ svn_revnum_t rev;
+ apr_pool_t *subpool;
+ const char *token;
+ void *root_baton;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)c", &rev, &token));
+ subpool = svn_pool_create(ds->pool);
+ SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
+ &root_baton));
+ store_token(ds, root_baton, token, FALSE, subpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token;
+ svn_revnum_t rev;
+ ra_svn_token_entry_t *entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)c",
+ &path, &rev, &token));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ path = svn_relpath_canonicalize(path, pool);
+ SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token, *child_token, *copy_path;
+ svn_revnum_t copy_rev;
+ ra_svn_token_entry_t *entry;
+ apr_pool_t *subpool;
+ void *child_baton;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?cr)", &path, &token,
+ &child_token, &copy_path, &copy_rev));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ subpool = svn_pool_create(entry->pool);
+ path = svn_relpath_canonicalize(path, pool);
+
+ /* Some operations pass COPY_PATH as a full URL (commits, etc.).
+ Others (replay, e.g.) deliver an fspath. That's ... annoying. */
+ if (copy_path)
+ {
+ if (svn_path_is_url(copy_path))
+ copy_path = svn_uri_canonicalize(copy_path, pool);
+ else
+ copy_path = svn_fspath__canonicalize(copy_path, pool);
+ }
+
+ SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
+ copy_rev, subpool, &child_baton));
+ store_token(ds, child_baton, child_token, FALSE, subpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token, *child_token;
+ svn_revnum_t rev;
+ ra_svn_token_entry_t *entry;
+ apr_pool_t *subpool;
+ void *child_baton;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?r)", &path, &token,
+ &child_token, &rev));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ subpool = svn_pool_create(entry->pool);
+ path = svn_relpath_canonicalize(path, pool);
+ SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
+ &child_baton));
+ store_token(ds, child_baton, child_token, FALSE, subpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token, *name;
+ svn_string_t *value;
+ ra_svn_token_entry_t *entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc(?s)", &token, &name,
+ &value));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
+ entry->pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+
+ /* Parse and look up the directory token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &token));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+
+ /* Close the directory and destroy the baton. */
+ SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
+ svn_hash_sets(ds->tokens, token, NULL);
+ svn_pool_destroy(entry->pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path;
+ const char *token;
+ ra_svn_token_entry_t *entry;
+
+ /* Parse parameters and look up the directory token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc", &path, &token));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+
+ /* Call the editor. */
+ SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token, *file_token, *copy_path;
+ svn_revnum_t copy_rev;
+ ra_svn_token_entry_t *entry, *file_entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?cr)", &path, &token,
+ &file_token, &copy_path, &copy_rev));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ ds->file_refs++;
+ path = svn_relpath_canonicalize(path, pool);
+
+ /* Some operations pass COPY_PATH as a full URL (commits, etc.).
+ Others (replay, e.g.) deliver an fspath. That's ... annoying. */
+ if (copy_path)
+ {
+ if (svn_path_is_url(copy_path))
+ copy_path = svn_uri_canonicalize(copy_path, pool);
+ else
+ copy_path = svn_fspath__canonicalize(copy_path, pool);
+ }
+
+ file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
+ SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev,
+ ds->file_pool, &file_entry->baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token, *file_token;
+ svn_revnum_t rev;
+ ra_svn_token_entry_t *entry, *file_entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?r)", &path, &token,
+ &file_token, &rev));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ ds->file_refs++;
+ path = svn_relpath_canonicalize(path, pool);
+ file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
+ SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool,
+ &file_entry->baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+ svn_txdelta_window_handler_t wh;
+ void *wh_baton;
+ char *base_checksum;
+
+ /* Parse arguments and look up the token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)",
+ &token, &base_checksum));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+ if (entry->dstream)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Apply-textdelta already active"));
+ entry->pool = svn_pool_create(ds->file_pool);
+ SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
+ entry->pool, &wh, &wh_baton));
+ entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+ svn_string_t *str;
+
+ /* Parse arguments and look up the token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &token, &str));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+ if (!entry->dstream)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Apply-textdelta not active"));
+ SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+
+ /* Parse arguments and look up the token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &token));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+ if (!entry->dstream)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Apply-textdelta not active"));
+ SVN_CMD_ERR(svn_stream_close(entry->dstream));
+ entry->dstream = NULL;
+ svn_pool_destroy(entry->pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token, *name;
+ svn_string_t *value;
+ ra_svn_token_entry_t *entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc(?s)", &token, &name,
+ &value));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+ SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+ const char *text_checksum;
+
+ /* Parse arguments and look up the file token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)",
+ &token, &text_checksum));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+
+ /* Close the file and destroy the baton. */
+ SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
+ svn_hash_sets(ds->tokens, token, NULL);
+ if (--ds->file_refs == 0)
+ svn_pool_clear(ds->file_pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path;
+ const char *token;
+ ra_svn_token_entry_t *entry;
+
+ /* Parse parameters and look up the parent directory token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc", &path, &token));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+
+ /* Call the editor. */
+ SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
+ ds->done = TRUE;
+ if (ds->aborted)
+ *ds->aborted = FALSE;
+ return svn_ra_svn__write_cmd_response(conn, pool, "");
+}
+
+static svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ ds->done = TRUE;
+ if (ds->aborted)
+ *ds->aborted = TRUE;
+ SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
+ return svn_ra_svn__write_cmd_response(conn, pool, "");
+}
+
+static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ if (!ds->for_replay)
+ return svn_error_createf
+ (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
+ _("Command 'finish-replay' invalid outside of replays"));
+ ds->done = TRUE;
+ if (ds->aborted)
+ *ds->aborted = FALSE;
+ return SVN_NO_ERROR;
+}
+
+static const struct {
+ const char *cmd;
+ svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds);
+} ra_svn_edit_cmds[] = {
+ { "change-file-prop", ra_svn_handle_change_file_prop },
+ { "open-file", ra_svn_handle_open_file },
+ { "apply-textdelta", ra_svn_handle_apply_textdelta },
+ { "textdelta-chunk", ra_svn_handle_textdelta_chunk },
+ { "close-file", ra_svn_handle_close_file },
+ { "add-dir", ra_svn_handle_add_dir },
+ { "open-dir", ra_svn_handle_open_dir },
+ { "change-dir-prop", ra_svn_handle_change_dir_prop },
+ { "delete-entry", ra_svn_handle_delete_entry },
+ { "close-dir", ra_svn_handle_close_dir },
+ { "absent-dir", ra_svn_handle_absent_dir },
+ { "add-file", ra_svn_handle_add_file },
+ { "textdelta-end", ra_svn_handle_textdelta_end },
+ { "absent-file", ra_svn_handle_absent_file },
+ { "abort-edit", ra_svn_handle_abort_edit },
+ { "finish-replay", ra_svn_handle_finish_replay },
+ { "target-rev", ra_svn_handle_target_rev },
+ { "open-root", ra_svn_handle_open_root },
+ { "close-edit", ra_svn_handle_close_edit },
+ { NULL }
+};
+
+static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ void *baton)
+{
+ ra_svn_driver_state_t *ds = baton;
+ const char *cmd;
+ apr_array_header_t *params;
+
+ /* We blocked trying to send an error. Read and discard an editing
+ * command in order to avoid deadlock. */
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &cmd, &params));
+ if (strcmp(cmd, "abort-edit") == 0)
+ {
+ ds->done = TRUE;
+ svn_ra_svn__set_block_handler(conn, NULL, NULL);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_boolean_t *aborted,
+ svn_boolean_t for_replay)
+{
+ ra_svn_driver_state_t state;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *cmd;
+ int i;
+ svn_error_t *err, *write_err;
+ apr_array_header_t *params;
+
+ state.editor = editor;
+ state.edit_baton = edit_baton;
+ state.tokens = apr_hash_make(pool);
+ state.aborted = aborted;
+ state.done = FALSE;
+ state.pool = pool;
+ state.file_pool = svn_pool_create(pool);
+ state.file_refs = 0;
+ state.for_replay = for_replay;
+
+ while (!state.done)
+ {
+ svn_pool_clear(subpool);
+ if (editor)
+ {
+ SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, &params));
+ for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
+ if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0)
+ break;
+
+ if (ra_svn_edit_cmds[i].cmd)
+ err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state);
+ else if (strcmp(cmd, "failure") == 0)
+ {
+ /* While not really an editor command this can occur when
+ reporter->finish_report() fails before the first editor
+ command */
+ if (aborted)
+ *aborted = TRUE;
+ err = svn_ra_svn__handle_failure_status(params, pool);
+ return svn_error_compose_create(
+ err,
+ editor->abort_edit(edit_baton, subpool));
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
+ _("Unknown editor command '%s'"), cmd);
+ err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
+ }
+ }
+ else
+ {
+ const char* command = NULL;
+ SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command));
+ if (strcmp(command, "close-edit") == 0)
+ {
+ state.done = TRUE;
+ if (aborted)
+ *aborted = FALSE;
+ err = svn_ra_svn__write_cmd_response(conn, pool, "");
+ }
+ else
+ err = NULL;
+ }
+
+ if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
+ {
+ if (aborted)
+ *aborted = TRUE;
+ if (!state.done)
+ {
+ /* Abort the edit and use non-blocking I/O to write the error. */
+ if (editor)
+ svn_error_clear(editor->abort_edit(edit_baton, subpool));
+ svn_ra_svn__set_block_handler(conn, blocked_write, &state);
+ }
+ write_err = svn_ra_svn__write_cmd_failure(
+ conn, subpool,
+ svn_ra_svn__locate_real_error_child(err));
+ if (!write_err)
+ write_err = svn_ra_svn__flush(conn, subpool);
+ svn_ra_svn__set_block_handler(conn, NULL, NULL);
+ svn_error_clear(err);
+ SVN_ERR(write_err);
+ break;
+ }
+ SVN_ERR(err);
+ }
+
+ /* Read and discard editing commands until the edit is complete.
+ Hopefully, the other side will call another editor command, run
+ check_for_error, notice the error, write "abort-edit" at us, and
+ throw the error up a few levels on its side (possibly even
+ tossing it right back at us, which is why we can return
+ SVN_NO_ERROR below).
+
+ However, if the other side is way ahead of us, it might
+ completely finish the edit (or sequence of edit/revprops, for
+ "replay-range") before we send over our "failure". So we should
+ also stop if we see "success". (Then the other side will try to
+ interpret our "failure" as a command, which will itself fail...
+ The net effect is that whatever error we wrote to the other side
+ will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.)
+ */
+ while (!state.done)
+ {
+ svn_pool_clear(subpool);
+ err = svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, &params);
+ if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED)
+ {
+ /* Other side disconnected; that's no error. */
+ svn_error_clear(err);
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+ }
+ svn_error_clear(err);
+ if (strcmp(cmd, "abort-edit") == 0
+ || strcmp(cmd, "success") == 0)
+ state.done = TRUE;
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_boolean_t *aborted)
+{
+ return svn_ra_svn_drive_editor2(conn,
+ pool,
+ editor,
+ edit_baton,
+ aborted,
+ FALSE);
+}
OpenPOWER on IntegriCloud