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_ERR_ASSERT(!eb->got_status);
131 /* reset TX counter */
132 eb->conn->written_since_error_check = 0;
134 /* if we weren't asked to always check, wait for at least the next TX */
135 eb->conn->may_check_for_error = eb->conn->error_check_interval == 0;
137 /* any incoming data? */
138 if (svn_ra_svn__input_waiting(eb->conn, pool))
140 eb->got_status = TRUE;
141 SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
142 SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
143 /* We shouldn't get here if the consumer is doing its job. */
144 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
145 _("Successful edit status returned too soon"));
151 check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
153 return eb->conn->may_check_for_error
154 ? check_for_error_internal(eb, pool)
158 static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
161 ra_svn_edit_baton_t *eb = edit_baton;
163 SVN_ERR(check_for_error(eb, pool));
164 SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev));
168 static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
169 apr_pool_t *pool, void **root_baton)
171 ra_svn_edit_baton_t *eb = edit_baton;
172 const char *token = make_token('d', eb, pool);
174 SVN_ERR(check_for_error(eb, pool));
175 SVN_ERR(svn_ra_svn__write_cmd_open_root(eb->conn, pool, rev, token));
176 *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
180 static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
181 void *parent_baton, apr_pool_t *pool)
183 ra_svn_baton_t *b = parent_baton;
185 SVN_ERR(check_for_error(b->eb, pool));
186 SVN_ERR(svn_ra_svn__write_cmd_delete_entry(b->conn, pool,
187 path, rev, b->token));
191 static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
192 const char *copy_path,
193 svn_revnum_t copy_rev,
194 apr_pool_t *pool, void **child_baton)
196 ra_svn_baton_t *b = parent_baton;
197 const char *token = make_token('d', b->eb, pool);
199 SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
200 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
201 SVN_ERR(check_for_error(b->eb, pool));
202 SVN_ERR(svn_ra_svn__write_cmd_add_dir(b->conn, pool, path, b->token,
203 token, copy_path, copy_rev));
204 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
208 static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
209 svn_revnum_t rev, apr_pool_t *pool,
212 ra_svn_baton_t *b = parent_baton;
213 const char *token = make_token('d', b->eb, pool);
215 SVN_ERR(check_for_error(b->eb, pool));
216 SVN_ERR(svn_ra_svn__write_cmd_open_dir(b->conn, pool, path, b->token,
218 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
222 static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
223 const svn_string_t *value,
226 ra_svn_baton_t *b = dir_baton;
228 SVN_ERR(check_for_error(b->eb, pool));
229 SVN_ERR(svn_ra_svn__write_cmd_change_dir_prop(b->conn, pool, b->token,
234 static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
236 ra_svn_baton_t *b = dir_baton;
238 SVN_ERR(check_for_error(b->eb, pool));
239 SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token));
243 static svn_error_t *ra_svn_absent_dir(const char *path,
244 void *parent_baton, apr_pool_t *pool)
246 ra_svn_baton_t *b = parent_baton;
248 /* Avoid sending an unknown command if the other end doesn't support
250 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
253 SVN_ERR(check_for_error(b->eb, pool));
254 SVN_ERR(svn_ra_svn__write_cmd_absent_dir(b->conn, pool, path, b->token));
258 static svn_error_t *ra_svn_add_file(const char *path,
260 const char *copy_path,
261 svn_revnum_t copy_rev,
265 ra_svn_baton_t *b = parent_baton;
266 const char *token = make_token('c', b->eb, pool);
268 SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
269 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
270 SVN_ERR(check_for_error(b->eb, pool));
271 SVN_ERR(svn_ra_svn__write_cmd_add_file(b->conn, pool, path, b->token,
272 token, copy_path, copy_rev));
273 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
277 static svn_error_t *ra_svn_open_file(const char *path,
283 ra_svn_baton_t *b = parent_baton;
284 const char *token = make_token('c', b->eb, pool);
286 SVN_ERR(check_for_error(b->eb, b->pool));
287 SVN_ERR(svn_ra_svn__write_cmd_open_file(b->conn, pool, path, b->token,
289 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
293 static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
296 ra_svn_baton_t *b = baton;
299 SVN_ERR(check_for_error(b->eb, b->pool));
302 return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool,
306 static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
308 ra_svn_baton_t *b = baton;
310 SVN_ERR(check_for_error(b->eb, b->pool));
311 SVN_ERR(svn_ra_svn__write_cmd_textdelta_end(b->conn, b->pool, b->token));
315 static svn_error_t *ra_svn_apply_textdelta(void *file_baton,
316 const char *base_checksum,
318 svn_txdelta_window_handler_t *wh,
321 ra_svn_baton_t *b = file_baton;
322 svn_stream_t *diff_stream;
324 /* Tell the other side we're starting a text delta. */
325 SVN_ERR(check_for_error(b->eb, pool));
326 SVN_ERR(svn_ra_svn__write_cmd_apply_textdelta(b->conn, pool, b->token,
329 /* Transform the window stream to an svndiff stream. Reuse the
330 * file baton for the stream handler, since it has all the
331 * needed information. */
332 diff_stream = svn_stream_create(b, pool);
333 svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
334 svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
336 /* If the connection does not support SVNDIFF1 or if we don't want to use
337 * compression, use the non-compressing "version 0" implementation */
338 if ( svn_ra_svn_compression_level(b->conn) > 0
339 && svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_SVNDIFF1))
340 svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 1,
341 b->conn->compression_level, pool);
343 svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 0,
344 b->conn->compression_level, pool);
348 static svn_error_t *ra_svn_change_file_prop(void *file_baton,
350 const svn_string_t *value,
353 ra_svn_baton_t *b = file_baton;
355 SVN_ERR(check_for_error(b->eb, pool));
356 SVN_ERR(svn_ra_svn__write_cmd_change_file_prop(b->conn, pool,
357 b->token, name, value));
361 static svn_error_t *ra_svn_close_file(void *file_baton,
362 const char *text_checksum,
365 ra_svn_baton_t *b = file_baton;
367 SVN_ERR(check_for_error(b->eb, pool));
368 SVN_ERR(svn_ra_svn__write_cmd_close_file(b->conn, pool,
369 b->token, text_checksum));
373 static svn_error_t *ra_svn_absent_file(const char *path,
374 void *parent_baton, apr_pool_t *pool)
376 ra_svn_baton_t *b = parent_baton;
378 /* Avoid sending an unknown command if the other end doesn't support
380 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
383 SVN_ERR(check_for_error(b->eb, pool));
384 SVN_ERR(svn_ra_svn__write_cmd_absent_file(b->conn, pool, path, b->token));
388 static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
390 ra_svn_edit_baton_t *eb = edit_baton;
393 SVN_ERR_ASSERT(!eb->got_status);
394 eb->got_status = TRUE;
395 SVN_ERR(svn_ra_svn__write_cmd_close_edit(eb->conn, pool));
396 err = svn_ra_svn__read_cmd_response(eb->conn, pool, "");
399 svn_error_clear(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
403 SVN_ERR(eb->callback(eb->callback_baton));
407 static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
409 ra_svn_edit_baton_t *eb = edit_baton;
413 SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
414 SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
418 void svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
419 void **edit_baton, svn_ra_svn_conn_t *conn,
421 svn_ra_svn_edit_callback callback,
422 void *callback_baton)
424 svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool);
425 ra_svn_edit_baton_t *eb;
427 eb = apr_palloc(pool, sizeof(*eb));
429 eb->callback = callback;
430 eb->callback_baton = callback_baton;
432 eb->got_status = FALSE;
434 ra_svn_editor->set_target_revision = ra_svn_target_rev;
435 ra_svn_editor->open_root = ra_svn_open_root;
436 ra_svn_editor->delete_entry = ra_svn_delete_entry;
437 ra_svn_editor->add_directory = ra_svn_add_dir;
438 ra_svn_editor->open_directory = ra_svn_open_dir;
439 ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
440 ra_svn_editor->close_directory = ra_svn_close_dir;
441 ra_svn_editor->absent_directory = ra_svn_absent_dir;
442 ra_svn_editor->add_file = ra_svn_add_file;
443 ra_svn_editor->open_file = ra_svn_open_file;
444 ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
445 ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
446 ra_svn_editor->close_file = ra_svn_close_file;
447 ra_svn_editor->absent_file = ra_svn_absent_file;
448 ra_svn_editor->close_edit = ra_svn_close_edit;
449 ra_svn_editor->abort_edit = ra_svn_abort_edit;
451 *editor = ra_svn_editor;
454 svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor,
455 *edit_baton, NULL, NULL,
456 conn->shim_callbacks,
460 /* --- DRIVING AN EDITOR --- */
462 /* Store a token entry. The token string will be copied into pool. */
463 static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
464 void *baton, const char *token,
465 svn_boolean_t is_file,
468 ra_svn_token_entry_t *entry;
470 entry = apr_palloc(pool, sizeof(*entry));
471 entry->token = apr_pstrdup(pool, token);
472 entry->baton = baton;
473 entry->is_file = is_file;
474 entry->dstream = NULL;
476 svn_hash_sets(ds->tokens, entry->token, entry);
480 static svn_error_t *lookup_token(ra_svn_driver_state_t *ds, const char *token,
481 svn_boolean_t is_file,
482 ra_svn_token_entry_t **entry)
484 *entry = svn_hash_gets(ds->tokens, token);
485 if (!*entry || (*entry)->is_file != is_file)
486 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
487 _("Invalid file or dir token during edit"));
491 static svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
493 const apr_array_header_t *params,
494 ra_svn_driver_state_t *ds)
498 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
499 SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
503 static svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
505 const apr_array_header_t *params,
506 ra_svn_driver_state_t *ds)
513 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)c", &rev, &token));
514 subpool = svn_pool_create(ds->pool);
515 SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
517 store_token(ds, root_baton, token, FALSE, subpool);
521 static svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
523 const apr_array_header_t *params,
524 ra_svn_driver_state_t *ds)
526 const char *path, *token;
528 ra_svn_token_entry_t *entry;
530 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)c",
531 &path, &rev, &token));
532 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
533 path = svn_relpath_canonicalize(path, pool);
534 SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool));
538 static svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
540 const apr_array_header_t *params,
541 ra_svn_driver_state_t *ds)
543 const char *path, *token, *child_token, *copy_path;
544 svn_revnum_t copy_rev;
545 ra_svn_token_entry_t *entry;
549 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?cr)", &path, &token,
550 &child_token, ©_path, ©_rev));
551 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
552 subpool = svn_pool_create(entry->pool);
553 path = svn_relpath_canonicalize(path, pool);
555 /* Some operations pass COPY_PATH as a full URL (commits, etc.).
556 Others (replay, e.g.) deliver an fspath. That's ... annoying. */
559 if (svn_path_is_url(copy_path))
560 copy_path = svn_uri_canonicalize(copy_path, pool);
562 copy_path = svn_fspath__canonicalize(copy_path, pool);
565 SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
566 copy_rev, subpool, &child_baton));
567 store_token(ds, child_baton, child_token, FALSE, subpool);
571 static svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
573 const apr_array_header_t *params,
574 ra_svn_driver_state_t *ds)
576 const char *path, *token, *child_token;
578 ra_svn_token_entry_t *entry;
582 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?r)", &path, &token,
583 &child_token, &rev));
584 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
585 subpool = svn_pool_create(entry->pool);
586 path = svn_relpath_canonicalize(path, pool);
587 SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
589 store_token(ds, child_baton, child_token, FALSE, subpool);
593 static svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
595 const apr_array_header_t *params,
596 ra_svn_driver_state_t *ds)
598 const char *token, *name;
600 ra_svn_token_entry_t *entry;
602 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc(?s)", &token, &name,
604 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
605 SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
610 static svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
612 const apr_array_header_t *params,
613 ra_svn_driver_state_t *ds)
616 ra_svn_token_entry_t *entry;
618 /* Parse and look up the directory token. */
619 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &token));
620 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
622 /* Close the directory and destroy the baton. */
623 SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
624 svn_hash_sets(ds->tokens, token, NULL);
625 svn_pool_destroy(entry->pool);
629 static svn_error_t *ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn,
631 const apr_array_header_t *params,
632 ra_svn_driver_state_t *ds)
636 ra_svn_token_entry_t *entry;
638 /* Parse parameters and look up the directory token. */
639 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc", &path, &token));
640 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
642 /* Call the editor. */
643 SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool));
647 static svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
649 const apr_array_header_t *params,
650 ra_svn_driver_state_t *ds)
652 const char *path, *token, *file_token, *copy_path;
653 svn_revnum_t copy_rev;
654 ra_svn_token_entry_t *entry, *file_entry;
656 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?cr)", &path, &token,
657 &file_token, ©_path, ©_rev));
658 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
660 path = svn_relpath_canonicalize(path, pool);
662 /* Some operations pass COPY_PATH as a full URL (commits, etc.).
663 Others (replay, e.g.) deliver an fspath. That's ... annoying. */
666 if (svn_path_is_url(copy_path))
667 copy_path = svn_uri_canonicalize(copy_path, pool);
669 copy_path = svn_fspath__canonicalize(copy_path, pool);
672 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
673 SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev,
674 ds->file_pool, &file_entry->baton));
678 static svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
680 const apr_array_header_t *params,
681 ra_svn_driver_state_t *ds)
683 const char *path, *token, *file_token;
685 ra_svn_token_entry_t *entry, *file_entry;
687 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?r)", &path, &token,
689 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
691 path = svn_relpath_canonicalize(path, pool);
692 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
693 SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool,
694 &file_entry->baton));
698 static svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
700 const apr_array_header_t *params,
701 ra_svn_driver_state_t *ds)
704 ra_svn_token_entry_t *entry;
705 svn_txdelta_window_handler_t wh;
709 /* Parse arguments and look up the token. */
710 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)",
711 &token, &base_checksum));
712 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
714 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
715 _("Apply-textdelta already active"));
716 entry->pool = svn_pool_create(ds->file_pool);
717 SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
718 entry->pool, &wh, &wh_baton));
719 entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
723 static svn_error_t *ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn,
725 const apr_array_header_t *params,
726 ra_svn_driver_state_t *ds)
729 ra_svn_token_entry_t *entry;
732 /* Parse arguments and look up the token. */
733 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &token, &str));
734 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
736 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
737 _("Apply-textdelta not active"));
738 SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
742 static svn_error_t *ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn,
744 const apr_array_header_t *params,
745 ra_svn_driver_state_t *ds)
748 ra_svn_token_entry_t *entry;
750 /* Parse arguments and look up the token. */
751 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &token));
752 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
754 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
755 _("Apply-textdelta not active"));
756 SVN_CMD_ERR(svn_stream_close(entry->dstream));
757 entry->dstream = NULL;
758 svn_pool_destroy(entry->pool);
762 static svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
764 const apr_array_header_t *params,
765 ra_svn_driver_state_t *ds)
767 const char *token, *name;
769 ra_svn_token_entry_t *entry;
771 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc(?s)", &token, &name,
773 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
774 SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
778 static svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
780 const apr_array_header_t *params,
781 ra_svn_driver_state_t *ds)
784 ra_svn_token_entry_t *entry;
785 const char *text_checksum;
787 /* Parse arguments and look up the file token. */
788 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)",
789 &token, &text_checksum));
790 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
792 /* Close the file and destroy the baton. */
793 SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
794 svn_hash_sets(ds->tokens, token, NULL);
795 if (--ds->file_refs == 0)
796 svn_pool_clear(ds->file_pool);
800 static svn_error_t *ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn,
802 const apr_array_header_t *params,
803 ra_svn_driver_state_t *ds)
807 ra_svn_token_entry_t *entry;
809 /* Parse parameters and look up the parent directory token. */
810 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc", &path, &token));
811 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
813 /* Call the editor. */
814 SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool));
818 static svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
820 const apr_array_header_t *params,
821 ra_svn_driver_state_t *ds)
823 SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
826 *ds->aborted = FALSE;
827 return svn_ra_svn__write_cmd_response(conn, pool, "");
830 static svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
832 const apr_array_header_t *params,
833 ra_svn_driver_state_t *ds)
838 SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
839 return svn_ra_svn__write_cmd_response(conn, pool, "");
842 static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
844 const apr_array_header_t *params,
845 ra_svn_driver_state_t *ds)
848 return svn_error_createf
849 (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
850 _("Command 'finish-replay' invalid outside of replays"));
853 *ds->aborted = FALSE;
857 static const struct {
859 svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
860 const apr_array_header_t *params,
861 ra_svn_driver_state_t *ds);
862 } ra_svn_edit_cmds[] = {
863 { "change-file-prop", ra_svn_handle_change_file_prop },
864 { "open-file", ra_svn_handle_open_file },
865 { "apply-textdelta", ra_svn_handle_apply_textdelta },
866 { "textdelta-chunk", ra_svn_handle_textdelta_chunk },
867 { "close-file", ra_svn_handle_close_file },
868 { "add-dir", ra_svn_handle_add_dir },
869 { "open-dir", ra_svn_handle_open_dir },
870 { "change-dir-prop", ra_svn_handle_change_dir_prop },
871 { "delete-entry", ra_svn_handle_delete_entry },
872 { "close-dir", ra_svn_handle_close_dir },
873 { "absent-dir", ra_svn_handle_absent_dir },
874 { "add-file", ra_svn_handle_add_file },
875 { "textdelta-end", ra_svn_handle_textdelta_end },
876 { "absent-file", ra_svn_handle_absent_file },
877 { "abort-edit", ra_svn_handle_abort_edit },
878 { "finish-replay", ra_svn_handle_finish_replay },
879 { "target-rev", ra_svn_handle_target_rev },
880 { "open-root", ra_svn_handle_open_root },
881 { "close-edit", ra_svn_handle_close_edit },
885 static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
888 ra_svn_driver_state_t *ds = baton;
890 apr_array_header_t *params;
892 /* We blocked trying to send an error. Read and discard an editing
893 * command in order to avoid deadlock. */
894 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &cmd, ¶ms));
895 if (strcmp(cmd, "abort-edit") == 0)
898 svn_ra_svn__set_block_handler(conn, NULL, NULL);
903 svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
905 const svn_delta_editor_t *editor,
907 svn_boolean_t *aborted,
908 svn_boolean_t for_replay)
910 ra_svn_driver_state_t state;
911 apr_pool_t *subpool = svn_pool_create(pool);
914 svn_error_t *err, *write_err;
915 apr_array_header_t *params;
917 state.editor = editor;
918 state.edit_baton = edit_baton;
919 state.tokens = apr_hash_make(pool);
920 state.aborted = aborted;
923 state.file_pool = svn_pool_create(pool);
925 state.for_replay = for_replay;
929 svn_pool_clear(subpool);
932 SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms));
933 for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
934 if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0)
937 if (ra_svn_edit_cmds[i].cmd)
938 err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state);
939 else if (strcmp(cmd, "failure") == 0)
941 /* While not really an editor command this can occur when
942 reporter->finish_report() fails before the first editor
946 err = svn_ra_svn__handle_failure_status(params, pool);
947 return svn_error_compose_create(
949 editor->abort_edit(edit_baton, subpool));
953 err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
954 _("Unknown editor command '%s'"), cmd);
955 err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
960 const char* command = NULL;
961 SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command));
962 if (strcmp(command, "close-edit") == 0)
967 err = svn_ra_svn__write_cmd_response(conn, pool, "");
973 if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
979 /* Abort the edit and use non-blocking I/O to write the error. */
981 svn_error_clear(editor->abort_edit(edit_baton, subpool));
982 svn_ra_svn__set_block_handler(conn, blocked_write, &state);
984 write_err = svn_ra_svn__write_cmd_failure(
986 svn_ra_svn__locate_real_error_child(err));
988 write_err = svn_ra_svn__flush(conn, subpool);
989 svn_ra_svn__set_block_handler(conn, NULL, NULL);
990 svn_error_clear(err);
997 /* Read and discard editing commands until the edit is complete.
998 Hopefully, the other side will call another editor command, run
999 check_for_error, notice the error, write "abort-edit" at us, and
1000 throw the error up a few levels on its side (possibly even
1001 tossing it right back at us, which is why we can return
1002 SVN_NO_ERROR below).
1004 However, if the other side is way ahead of us, it might
1005 completely finish the edit (or sequence of edit/revprops, for
1006 "replay-range") before we send over our "failure". So we should
1007 also stop if we see "success". (Then the other side will try to
1008 interpret our "failure" as a command, which will itself fail...
1009 The net effect is that whatever error we wrote to the other side
1010 will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.)
1014 svn_pool_clear(subpool);
1015 err = svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms);
1016 if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED)
1018 /* Other side disconnected; that's no error. */
1019 svn_error_clear(err);
1020 svn_pool_destroy(subpool);
1021 return SVN_NO_ERROR;
1023 svn_error_clear(err);
1024 if (strcmp(cmd, "abort-edit") == 0
1025 || strcmp(cmd, "success") == 0)
1029 svn_pool_destroy(subpool);
1030 return SVN_NO_ERROR;
1033 svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1034 const svn_delta_editor_t *editor,
1036 svn_boolean_t *aborted)
1038 return svn_ra_svn_drive_editor2(conn,