2 * editorp.c : Driving and consuming an editor across an svn connection
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
26 #define APR_WANT_STRFUNC
28 #include <apr_general.h>
29 #include <apr_strings.h>
32 #include "svn_types.h"
33 #include "svn_string.h"
34 #include "svn_error.h"
35 #include "svn_delta.h"
36 #include "svn_dirent_uri.h"
37 #include "svn_ra_svn.h"
39 #include "svn_pools.h"
40 #include "svn_private_config.h"
42 #include "private/svn_fspath.h"
43 #include "private/svn_editor.h"
48 * Both the client and server in the svn protocol need to drive and
49 * consume editors. For a commit, the client drives and the server
50 * consumes; for an update/switch/status/diff, the server drives and
51 * the client consumes. This file provides a generic framework for
52 * marshalling and unmarshalling editor operations over an svn
53 * connection; both ends are useful for both server and client.
56 typedef struct ra_svn_edit_baton_t {
57 svn_ra_svn_conn_t *conn;
58 svn_ra_svn_edit_callback callback; /* Called on successful completion. */
61 svn_boolean_t got_status;
62 } ra_svn_edit_baton_t;
64 /* Works for both directories and files. */
65 typedef struct ra_svn_baton_t {
66 svn_ra_svn_conn_t *conn;
68 ra_svn_edit_baton_t *eb;
72 typedef struct ra_svn_driver_state_t {
73 const svn_delta_editor_t *editor;
76 svn_boolean_t *aborted;
79 apr_pool_t *file_pool;
81 svn_boolean_t for_replay;
82 } ra_svn_driver_state_t;
84 /* Works for both directories and files; however, the pool handling is
85 different for files. To save space during commits (where file
86 batons generally last until the end of the commit), token entries
87 for files are all created in a single reference-counted pool (the
88 file_pool member of the driver state structure), which is cleared
89 at close_file time when the reference count hits zero. So the pool
90 field in this structure is vestigial for files, and we use it for a
91 different purpose instead: at apply-textdelta time, we set it to a
92 subpool of the file pool, which is destroyed in textdelta-end. */
93 typedef struct ra_svn_token_entry_t {
96 svn_boolean_t is_file;
97 svn_stream_t *dstream; /* svndiff stream for apply_textdelta */
99 } ra_svn_token_entry_t;
101 /* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */
103 static const char *make_token(char type, ra_svn_edit_baton_t *eb,
106 return apr_psprintf(pool, "%c%d", type, eb->next_token++);
109 static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn,
111 ra_svn_edit_baton_t *eb,
116 b = apr_palloc(pool, sizeof(*b));
124 /* Check for an early error status report from the consumer. If we
125 * get one, abort the edit and return the error. */
127 check_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
129 svn_boolean_t available;
130 SVN_ERR_ASSERT(!eb->got_status);
132 /* reset TX counter */
133 eb->conn->written_since_error_check = 0;
135 /* if we weren't asked to always check, wait for at least the next TX */
136 eb->conn->may_check_for_error = eb->conn->error_check_interval == 0;
138 /* any incoming data? */
139 SVN_ERR(svn_ra_svn__data_available(eb->conn, &available));
142 eb->got_status = TRUE;
143 SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
144 SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
145 /* We shouldn't get here if the consumer is doing its job. */
146 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
147 _("Successful edit status returned too soon"));
153 check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
155 return eb->conn->may_check_for_error
156 ? check_for_error_internal(eb, pool)
160 static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
163 ra_svn_edit_baton_t *eb = edit_baton;
165 SVN_ERR(check_for_error(eb, pool));
166 SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev));
170 static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
171 apr_pool_t *pool, void **root_baton)
173 ra_svn_edit_baton_t *eb = edit_baton;
174 const char *token = make_token('d', eb, pool);
176 SVN_ERR(check_for_error(eb, pool));
177 SVN_ERR(svn_ra_svn__write_cmd_open_root(eb->conn, pool, rev, token));
178 *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
182 static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
183 void *parent_baton, apr_pool_t *pool)
185 ra_svn_baton_t *b = parent_baton;
187 SVN_ERR(check_for_error(b->eb, pool));
188 SVN_ERR(svn_ra_svn__write_cmd_delete_entry(b->conn, pool,
189 path, rev, b->token));
193 static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
194 const char *copy_path,
195 svn_revnum_t copy_rev,
196 apr_pool_t *pool, void **child_baton)
198 ra_svn_baton_t *b = parent_baton;
199 const char *token = make_token('d', b->eb, pool);
201 SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
202 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
203 SVN_ERR(check_for_error(b->eb, pool));
204 SVN_ERR(svn_ra_svn__write_cmd_add_dir(b->conn, pool, path, b->token,
205 token, copy_path, copy_rev));
206 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
210 static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
211 svn_revnum_t rev, apr_pool_t *pool,
214 ra_svn_baton_t *b = parent_baton;
215 const char *token = make_token('d', b->eb, pool);
217 SVN_ERR(check_for_error(b->eb, pool));
218 SVN_ERR(svn_ra_svn__write_cmd_open_dir(b->conn, pool, path, b->token,
220 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
224 static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
225 const svn_string_t *value,
228 ra_svn_baton_t *b = dir_baton;
230 SVN_ERR(check_for_error(b->eb, pool));
231 SVN_ERR(svn_ra_svn__write_cmd_change_dir_prop(b->conn, pool, b->token,
236 static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
238 ra_svn_baton_t *b = dir_baton;
240 SVN_ERR(check_for_error(b->eb, pool));
241 SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token));
245 static svn_error_t *ra_svn_absent_dir(const char *path,
246 void *parent_baton, apr_pool_t *pool)
248 ra_svn_baton_t *b = parent_baton;
250 /* Avoid sending an unknown command if the other end doesn't support
252 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
255 SVN_ERR(check_for_error(b->eb, pool));
256 SVN_ERR(svn_ra_svn__write_cmd_absent_dir(b->conn, pool, path, b->token));
260 static svn_error_t *ra_svn_add_file(const char *path,
262 const char *copy_path,
263 svn_revnum_t copy_rev,
267 ra_svn_baton_t *b = parent_baton;
268 const char *token = make_token('c', b->eb, pool);
270 SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
271 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
272 SVN_ERR(check_for_error(b->eb, pool));
273 SVN_ERR(svn_ra_svn__write_cmd_add_file(b->conn, pool, path, b->token,
274 token, copy_path, copy_rev));
275 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
279 static svn_error_t *ra_svn_open_file(const char *path,
285 ra_svn_baton_t *b = parent_baton;
286 const char *token = make_token('c', b->eb, pool);
288 SVN_ERR(check_for_error(b->eb, b->pool));
289 SVN_ERR(svn_ra_svn__write_cmd_open_file(b->conn, pool, path, b->token,
291 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
295 static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
298 ra_svn_baton_t *b = baton;
301 SVN_ERR(check_for_error(b->eb, b->pool));
304 return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool,
308 static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
310 ra_svn_baton_t *b = baton;
312 SVN_ERR(check_for_error(b->eb, b->pool));
313 SVN_ERR(svn_ra_svn__write_cmd_textdelta_end(b->conn, b->pool, b->token));
317 static svn_error_t *ra_svn_apply_textdelta(void *file_baton,
318 const char *base_checksum,
320 svn_txdelta_window_handler_t *wh,
323 ra_svn_baton_t *b = file_baton;
324 svn_stream_t *diff_stream;
326 /* Tell the other side we're starting a text delta. */
327 SVN_ERR(check_for_error(b->eb, pool));
328 SVN_ERR(svn_ra_svn__write_cmd_apply_textdelta(b->conn, pool, b->token,
331 /* Transform the window stream to an svndiff stream. Reuse the
332 * file baton for the stream handler, since it has all the
333 * needed information. */
334 diff_stream = svn_stream_create(b, pool);
335 svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
336 svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
338 /* If the connection does not support SVNDIFF1 or if we don't want to use
339 * compression, use the non-compressing "version 0" implementation */
340 if ( svn_ra_svn_compression_level(b->conn) > 0
341 && svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_SVNDIFF1))
342 svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 1,
343 b->conn->compression_level, pool);
345 svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 0,
346 b->conn->compression_level, pool);
350 static svn_error_t *ra_svn_change_file_prop(void *file_baton,
352 const svn_string_t *value,
355 ra_svn_baton_t *b = file_baton;
357 SVN_ERR(check_for_error(b->eb, pool));
358 SVN_ERR(svn_ra_svn__write_cmd_change_file_prop(b->conn, pool,
359 b->token, name, value));
363 static svn_error_t *ra_svn_close_file(void *file_baton,
364 const char *text_checksum,
367 ra_svn_baton_t *b = file_baton;
369 SVN_ERR(check_for_error(b->eb, pool));
370 SVN_ERR(svn_ra_svn__write_cmd_close_file(b->conn, pool,
371 b->token, text_checksum));
375 static svn_error_t *ra_svn_absent_file(const char *path,
376 void *parent_baton, apr_pool_t *pool)
378 ra_svn_baton_t *b = parent_baton;
380 /* Avoid sending an unknown command if the other end doesn't support
382 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
385 SVN_ERR(check_for_error(b->eb, pool));
386 SVN_ERR(svn_ra_svn__write_cmd_absent_file(b->conn, pool, path, b->token));
390 static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
392 ra_svn_edit_baton_t *eb = edit_baton;
395 SVN_ERR_ASSERT(!eb->got_status);
396 eb->got_status = TRUE;
397 SVN_ERR(svn_ra_svn__write_cmd_close_edit(eb->conn, pool));
398 err = svn_error_trace(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
401 return svn_error_compose_create(
404 svn_ra_svn__write_cmd_abort_edit(eb->conn, pool)));
407 SVN_ERR(eb->callback(eb->callback_baton));
411 static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
413 ra_svn_edit_baton_t *eb = edit_baton;
417 SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
418 SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
422 void svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
423 void **edit_baton, svn_ra_svn_conn_t *conn,
425 svn_ra_svn_edit_callback callback,
426 void *callback_baton)
428 svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool);
429 ra_svn_edit_baton_t *eb;
431 eb = apr_palloc(pool, sizeof(*eb));
433 eb->callback = callback;
434 eb->callback_baton = callback_baton;
436 eb->got_status = FALSE;
438 ra_svn_editor->set_target_revision = ra_svn_target_rev;
439 ra_svn_editor->open_root = ra_svn_open_root;
440 ra_svn_editor->delete_entry = ra_svn_delete_entry;
441 ra_svn_editor->add_directory = ra_svn_add_dir;
442 ra_svn_editor->open_directory = ra_svn_open_dir;
443 ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
444 ra_svn_editor->close_directory = ra_svn_close_dir;
445 ra_svn_editor->absent_directory = ra_svn_absent_dir;
446 ra_svn_editor->add_file = ra_svn_add_file;
447 ra_svn_editor->open_file = ra_svn_open_file;
448 ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
449 ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
450 ra_svn_editor->close_file = ra_svn_close_file;
451 ra_svn_editor->absent_file = ra_svn_absent_file;
452 ra_svn_editor->close_edit = ra_svn_close_edit;
453 ra_svn_editor->abort_edit = ra_svn_abort_edit;
455 *editor = ra_svn_editor;
458 svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor,
459 *edit_baton, NULL, NULL,
460 conn->shim_callbacks,
464 /* --- DRIVING AN EDITOR --- */
466 /* Store a token entry. The token string will be copied into pool. */
467 static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
470 svn_boolean_t is_file,
473 ra_svn_token_entry_t *entry;
475 entry = apr_palloc(pool, sizeof(*entry));
476 entry->token = svn_string_dup(token, pool);
477 entry->baton = baton;
478 entry->is_file = is_file;
479 entry->dstream = NULL;
482 apr_hash_set(ds->tokens, entry->token->data, entry->token->len, entry);
487 static svn_error_t *lookup_token(ra_svn_driver_state_t *ds,
489 svn_boolean_t is_file,
490 ra_svn_token_entry_t **entry)
492 *entry = apr_hash_get(ds->tokens, token->data, token->len);
493 if (!*entry || (*entry)->is_file != is_file)
494 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
495 _("Invalid file or dir token during edit"));
499 static svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
501 const apr_array_header_t *params,
502 ra_svn_driver_state_t *ds)
506 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
507 SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
511 static svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
513 const apr_array_header_t *params,
514 ra_svn_driver_state_t *ds)
521 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)s", &rev, &token));
522 subpool = svn_pool_create(ds->pool);
523 SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
525 store_token(ds, root_baton, token, FALSE, subpool);
529 static svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
531 const apr_array_header_t *params,
532 ra_svn_driver_state_t *ds)
537 ra_svn_token_entry_t *entry;
539 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)s",
540 &path, &rev, &token));
541 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
542 path = svn_relpath_canonicalize(path, pool);
543 SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool));
547 static svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
549 const apr_array_header_t *params,
550 ra_svn_driver_state_t *ds)
552 const char *path, *copy_path;
553 svn_string_t *token, *child_token;
554 svn_revnum_t copy_rev;
555 ra_svn_token_entry_t *entry;
559 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?cr)", &path, &token,
560 &child_token, ©_path, ©_rev));
561 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
562 subpool = svn_pool_create(entry->pool);
563 path = svn_relpath_canonicalize(path, pool);
565 /* Some operations pass COPY_PATH as a full URL (commits, etc.).
566 Others (replay, e.g.) deliver an fspath. That's ... annoying. */
569 if (svn_path_is_url(copy_path))
570 copy_path = svn_uri_canonicalize(copy_path, pool);
572 copy_path = svn_fspath__canonicalize(copy_path, pool);
575 SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
576 copy_rev, subpool, &child_baton));
577 store_token(ds, child_baton, child_token, FALSE, subpool);
581 static svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
583 const apr_array_header_t *params,
584 ra_svn_driver_state_t *ds)
587 svn_string_t *token, *child_token;
589 ra_svn_token_entry_t *entry;
593 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?r)", &path, &token,
594 &child_token, &rev));
595 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
596 subpool = svn_pool_create(entry->pool);
597 path = svn_relpath_canonicalize(path, pool);
598 SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
600 store_token(ds, child_baton, child_token, FALSE, subpool);
604 static svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
606 const apr_array_header_t *params,
607 ra_svn_driver_state_t *ds)
612 ra_svn_token_entry_t *entry;
614 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "sc(?s)", &token, &name,
616 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
617 SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
622 static svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
624 const apr_array_header_t *params,
625 ra_svn_driver_state_t *ds)
628 ra_svn_token_entry_t *entry;
630 /* Parse and look up the directory token. */
631 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s", &token));
632 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
634 /* Close the directory and destroy the baton. */
635 SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
636 apr_hash_set(ds->tokens, token->data, token->len, NULL);
637 svn_pool_destroy(entry->pool);
641 static svn_error_t *ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn,
643 const apr_array_header_t *params,
644 ra_svn_driver_state_t *ds)
648 ra_svn_token_entry_t *entry;
650 /* Parse parameters and look up the directory token. */
651 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &path, &token));
652 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
654 /* Call the editor. */
655 SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool));
659 static svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
661 const apr_array_header_t *params,
662 ra_svn_driver_state_t *ds)
664 const char *path, *copy_path;
665 svn_string_t *token, *file_token;
666 svn_revnum_t copy_rev;
667 ra_svn_token_entry_t *entry, *file_entry;
669 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?cr)", &path, &token,
670 &file_token, ©_path, ©_rev));
671 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
674 /* The PATH should be canonical .. but never trust incoming data. */
675 if (!svn_relpath_is_canonical(path))
676 path = svn_relpath_canonicalize(path, pool);
678 /* Some operations pass COPY_PATH as a full URL (commits, etc.).
679 Others (replay, e.g.) deliver an fspath. That's ... annoying. */
682 if (svn_path_is_url(copy_path))
683 copy_path = svn_uri_canonicalize(copy_path, pool);
685 copy_path = svn_fspath__canonicalize(copy_path, pool);
688 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
689 SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev,
690 ds->file_pool, &file_entry->baton));
694 static svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
696 const apr_array_header_t *params,
697 ra_svn_driver_state_t *ds)
700 svn_string_t *token, *file_token;
702 ra_svn_token_entry_t *entry, *file_entry;
704 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?r)", &path, &token,
706 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
709 /* The PATH should be canonical .. but never trust incoming data. */
710 if (!svn_relpath_is_canonical(path))
711 path = svn_relpath_canonicalize(path, pool);
713 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
714 SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool,
715 &file_entry->baton));
719 static svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
721 const apr_array_header_t *params,
722 ra_svn_driver_state_t *ds)
725 ra_svn_token_entry_t *entry;
726 svn_txdelta_window_handler_t wh;
730 /* Parse arguments and look up the token. */
731 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s(?c)",
732 &token, &base_checksum));
733 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
735 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
736 _("Apply-textdelta already active"));
737 entry->pool = svn_pool_create(ds->file_pool);
738 SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
739 entry->pool, &wh, &wh_baton));
740 entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
744 static svn_error_t *ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn,
746 const apr_array_header_t *params,
747 ra_svn_driver_state_t *ds)
750 ra_svn_token_entry_t *entry;
753 /* Parse arguments and look up the token. */
754 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ss", &token, &str));
755 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
757 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
758 _("Apply-textdelta not active"));
759 SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
763 static svn_error_t *ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn,
765 const apr_array_header_t *params,
766 ra_svn_driver_state_t *ds)
769 ra_svn_token_entry_t *entry;
771 /* Parse arguments and look up the token. */
772 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s", &token));
773 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
775 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
776 _("Apply-textdelta not active"));
777 SVN_CMD_ERR(svn_stream_close(entry->dstream));
778 entry->dstream = NULL;
779 svn_pool_destroy(entry->pool);
783 static svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
785 const apr_array_header_t *params,
786 ra_svn_driver_state_t *ds)
789 svn_string_t *token, *value;
790 ra_svn_token_entry_t *entry;
792 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "sc(?s)", &token, &name,
794 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
795 SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
799 static svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
801 const apr_array_header_t *params,
802 ra_svn_driver_state_t *ds)
805 ra_svn_token_entry_t *entry;
806 const char *text_checksum;
808 /* Parse arguments and look up the file token. */
809 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s(?c)",
810 &token, &text_checksum));
811 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
813 /* Close the file and destroy the baton. */
814 SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
815 apr_hash_set(ds->tokens, token->data, token->len, NULL);
816 if (--ds->file_refs == 0)
817 svn_pool_clear(ds->file_pool);
821 static svn_error_t *ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn,
823 const apr_array_header_t *params,
824 ra_svn_driver_state_t *ds)
828 ra_svn_token_entry_t *entry;
830 /* Parse parameters and look up the parent directory token. */
831 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &path, &token));
832 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
834 /* Call the editor. */
835 SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool));
839 static svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
841 const apr_array_header_t *params,
842 ra_svn_driver_state_t *ds)
844 SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
847 *ds->aborted = FALSE;
848 return svn_ra_svn__write_cmd_response(conn, pool, "");
851 static svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
853 const apr_array_header_t *params,
854 ra_svn_driver_state_t *ds)
859 SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
860 return svn_ra_svn__write_cmd_response(conn, pool, "");
863 static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
865 const apr_array_header_t *params,
866 ra_svn_driver_state_t *ds)
869 return svn_error_createf
870 (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
871 _("Command 'finish-replay' invalid outside of replays"));
874 *ds->aborted = FALSE;
878 static const struct {
880 svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
881 const apr_array_header_t *params,
882 ra_svn_driver_state_t *ds);
883 } ra_svn_edit_cmds[] = {
884 { "change-file-prop", ra_svn_handle_change_file_prop },
885 { "open-file", ra_svn_handle_open_file },
886 { "apply-textdelta", ra_svn_handle_apply_textdelta },
887 { "textdelta-chunk", ra_svn_handle_textdelta_chunk },
888 { "close-file", ra_svn_handle_close_file },
889 { "add-dir", ra_svn_handle_add_dir },
890 { "open-dir", ra_svn_handle_open_dir },
891 { "change-dir-prop", ra_svn_handle_change_dir_prop },
892 { "delete-entry", ra_svn_handle_delete_entry },
893 { "close-dir", ra_svn_handle_close_dir },
894 { "absent-dir", ra_svn_handle_absent_dir },
895 { "add-file", ra_svn_handle_add_file },
896 { "textdelta-end", ra_svn_handle_textdelta_end },
897 { "absent-file", ra_svn_handle_absent_file },
898 { "abort-edit", ra_svn_handle_abort_edit },
899 { "finish-replay", ra_svn_handle_finish_replay },
900 { "target-rev", ra_svn_handle_target_rev },
901 { "open-root", ra_svn_handle_open_root },
902 { "close-edit", ra_svn_handle_close_edit },
906 static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
909 ra_svn_driver_state_t *ds = baton;
911 apr_array_header_t *params;
913 /* We blocked trying to send an error. Read and discard an editing
914 * command in order to avoid deadlock. */
915 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &cmd, ¶ms));
916 if (strcmp(cmd, "abort-edit") == 0)
919 svn_ra_svn__set_block_handler(conn, NULL, NULL);
924 svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
926 const svn_delta_editor_t *editor,
928 svn_boolean_t *aborted,
929 svn_boolean_t for_replay)
931 ra_svn_driver_state_t state;
932 apr_pool_t *subpool = svn_pool_create(pool);
935 svn_error_t *err, *write_err;
936 apr_array_header_t *params;
938 state.editor = editor;
939 state.edit_baton = edit_baton;
940 state.tokens = apr_hash_make(pool);
941 state.aborted = aborted;
944 state.file_pool = svn_pool_create(pool);
946 state.for_replay = for_replay;
950 svn_pool_clear(subpool);
953 SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms));
954 for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
955 if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0)
958 if (ra_svn_edit_cmds[i].cmd)
959 err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state);
960 else if (strcmp(cmd, "failure") == 0)
962 /* While not really an editor command this can occur when
963 reporter->finish_report() fails before the first editor
967 err = svn_ra_svn__handle_failure_status(params, pool);
968 return svn_error_compose_create(
970 editor->abort_edit(edit_baton, subpool));
974 err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
975 _("Unknown editor command '%s'"), cmd);
976 err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
981 const char* command = NULL;
982 SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command));
983 if (strcmp(command, "close-edit") == 0)
988 err = svn_ra_svn__write_cmd_response(conn, pool, "");
994 if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
1000 /* Abort the edit and use non-blocking I/O to write the error. */
1003 err = svn_error_compose_create(
1005 svn_error_trace(editor->abort_edit(edit_baton,
1008 svn_ra_svn__set_block_handler(conn, blocked_write, &state);
1010 write_err = svn_ra_svn__write_cmd_failure(
1012 svn_ra_svn__locate_real_error_child(err));
1014 write_err = svn_ra_svn__flush(conn, subpool);
1015 svn_ra_svn__set_block_handler(conn, NULL, NULL);
1016 svn_error_clear(err); /* We just sent this error */
1023 /* Read and discard editing commands until the edit is complete.
1024 Hopefully, the other side will call another editor command, run
1025 check_for_error, notice the error, write "abort-edit" at us, and
1026 throw the error up a few levels on its side (possibly even
1027 tossing it right back at us, which is why we can return
1028 SVN_NO_ERROR below).
1030 However, if the other side is way ahead of us, it might
1031 completely finish the edit (or sequence of edit/revprops, for
1032 "replay-range") before we send over our "failure". So we should
1033 also stop if we see "success". (Then the other side will try to
1034 interpret our "failure" as a command, which will itself fail...
1035 The net effect is that whatever error we wrote to the other side
1036 will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.)
1040 svn_pool_clear(subpool);
1041 err = svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms);
1042 if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED)
1044 /* Other side disconnected; that's no error. */
1045 svn_error_clear(err);
1046 svn_pool_destroy(subpool);
1047 return SVN_NO_ERROR;
1049 svn_error_clear(err);
1050 if (strcmp(cmd, "abort-edit") == 0
1051 || strcmp(cmd, "success") == 0)
1055 svn_pool_destroy(subpool);
1056 return SVN_NO_ERROR;
1059 svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1060 const svn_delta_editor_t *editor,
1062 svn_boolean_t *aborted)
1064 return svn_ra_svn_drive_editor2(conn,