In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_svn / editorp.c
blob266c8750d5b74a2d93b8c7fe7e57155b127f01c8
1 /*
2 * editorp.c : Driving and consuming an editor across an svn connection
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h>
23 #include <apr_general.h>
24 #include <apr_strings.h>
25 #include <apr_md5.h>
27 #include <assert.h>
29 #include "svn_types.h"
30 #include "svn_string.h"
31 #include "svn_error.h"
32 #include "svn_path.h"
33 #include "svn_delta.h"
34 #include "svn_ra_svn.h"
35 #include "svn_pools.h"
36 #include "svn_private_config.h"
38 #include "ra_svn.h"
41 * Both the client and server in the svn protocol need to drive and
42 * consume editors. For a commit, the client drives and the server
43 * consumes; for an update/switch/status/diff, the server drives and
44 * the client consumes. This file provides a generic framework for
45 * marshalling and unmarshalling editor operations over an svn
46 * connection; both ends are useful for both server and client.
49 typedef struct {
50 svn_ra_svn_conn_t *conn;
51 svn_ra_svn_edit_callback callback; /* Called on successful completion. */
52 void *callback_baton;
53 int next_token;
54 svn_boolean_t got_status;
55 } ra_svn_edit_baton_t;
57 /* Works for both directories and files. */
58 typedef struct {
59 svn_ra_svn_conn_t *conn;
60 apr_pool_t *pool;
61 ra_svn_edit_baton_t *eb;
62 const char *token;
63 } ra_svn_baton_t;
65 typedef struct {
66 const svn_delta_editor_t *editor;
67 void *edit_baton;
68 apr_hash_t *tokens;
69 svn_boolean_t *aborted;
70 svn_boolean_t done;
71 apr_pool_t *pool;
72 apr_pool_t *file_pool;
73 int file_refs;
74 svn_boolean_t for_replay;
75 } ra_svn_driver_state_t;
77 /* Works for both directories and files; however, the pool handling is
78 different for files. To save space during commits (where file
79 batons generally last until the end of the commit), token entries
80 for files are all created in a single reference-counted pool (the
81 file_pool member of the driver state structure), which is cleared
82 at close_file time when the reference count hits zero. So the pool
83 field in this structure is vestigial for files, and we use it for a
84 different purpose instead: at apply-textdelta time, we set it to a
85 subpool of the file pool, which is destroyed in textdelta-end. */
86 typedef struct {
87 const char *token;
88 void *baton;
89 svn_boolean_t is_file;
90 svn_stream_t *dstream; /* svndiff stream for apply_textdelta */
91 apr_pool_t *pool;
92 } ra_svn_token_entry_t;
94 /* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */
96 static const char *make_token(char type, ra_svn_edit_baton_t *eb,
97 apr_pool_t *pool)
99 return apr_psprintf(pool, "%c%d", type, eb->next_token++);
102 static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn,
103 apr_pool_t *pool,
104 ra_svn_edit_baton_t *eb,
105 const char *token)
107 ra_svn_baton_t *b;
109 b = apr_palloc(pool, sizeof(*b));
110 b->conn = conn;
111 b->pool = pool;
112 b->eb = eb;
113 b->token = token;
114 return b;
117 /* Check for an early error status report from the consumer. If we
118 * get one, abort the edit and return the error. */
119 static svn_error_t *check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
121 assert(!eb->got_status);
122 if (svn_ra_svn__input_waiting(eb->conn, pool))
124 eb->got_status = TRUE;
125 SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", ""));
126 SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, ""));
127 /* We shouldn't get here if the consumer is doing its job. */
128 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
129 _("Successful edit status returned too soon"));
131 return SVN_NO_ERROR;
134 static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
135 apr_pool_t *pool)
137 ra_svn_edit_baton_t *eb = edit_baton;
139 SVN_ERR(check_for_error(eb, pool));
140 SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "target-rev", "r", rev));
141 return SVN_NO_ERROR;
144 static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
145 apr_pool_t *pool, void **root_baton)
147 ra_svn_edit_baton_t *eb = edit_baton;
148 const char *token = make_token('d', eb, pool);
150 SVN_ERR(check_for_error(eb, pool));
151 SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "open-root", "(?r)c", rev,
152 token));
153 *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
154 return SVN_NO_ERROR;
157 static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
158 void *parent_baton, apr_pool_t *pool)
160 ra_svn_baton_t *b = parent_baton;
162 SVN_ERR(check_for_error(b->eb, pool));
163 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "delete-entry", "c(?r)c",
164 path, rev, b->token));
165 return SVN_NO_ERROR;
168 static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
169 const char *copy_path,
170 svn_revnum_t copy_rev,
171 apr_pool_t *pool, void **child_baton)
173 ra_svn_baton_t *b = parent_baton;
174 const char *token = make_token('d', b->eb, pool);
176 assert((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
177 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
178 SVN_ERR(check_for_error(b->eb, pool));
179 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "add-dir", "ccc(?cr)", path,
180 b->token, token, copy_path, copy_rev));
181 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
182 return SVN_NO_ERROR;
185 static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
186 svn_revnum_t rev, apr_pool_t *pool,
187 void **child_baton)
189 ra_svn_baton_t *b = parent_baton;
190 const char *token = make_token('d', b->eb, pool);
192 SVN_ERR(check_for_error(b->eb, pool));
193 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "open-dir", "ccc(?r)",
194 path, b->token, token, rev));
195 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
196 return SVN_NO_ERROR;
199 static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
200 const svn_string_t *value,
201 apr_pool_t *pool)
203 ra_svn_baton_t *b = dir_baton;
205 SVN_ERR(check_for_error(b->eb, pool));
206 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "change-dir-prop", "cc(?s)",
207 b->token, name, value));
208 return SVN_NO_ERROR;
211 static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
213 ra_svn_baton_t *b = dir_baton;
215 SVN_ERR(check_for_error(b->eb, pool));
216 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "close-dir", "c", b->token));
217 return SVN_NO_ERROR;
220 static svn_error_t *ra_svn_absent_dir(const char *path,
221 void *parent_baton, apr_pool_t *pool)
223 ra_svn_baton_t *b = parent_baton;
225 /* Avoid sending an unknown command if the other end doesn't support
226 absent-dir. */
227 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
228 return SVN_NO_ERROR;
230 SVN_ERR(check_for_error(b->eb, pool));
231 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "absent-dir", "cc", path,
232 b->token));
233 return SVN_NO_ERROR;
236 static svn_error_t *ra_svn_add_file(const char *path,
237 void *parent_baton,
238 const char *copy_path,
239 svn_revnum_t copy_rev,
240 apr_pool_t *pool,
241 void **file_baton)
243 ra_svn_baton_t *b = parent_baton;
244 const char *token = make_token('c', b->eb, pool);
246 assert((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
247 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
248 SVN_ERR(check_for_error(b->eb, pool));
249 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "add-file", "ccc(?cr)", path,
250 b->token, token, copy_path, copy_rev));
251 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
252 return SVN_NO_ERROR;
255 static svn_error_t *ra_svn_open_file(const char *path,
256 void *parent_baton,
257 svn_revnum_t rev,
258 apr_pool_t *pool,
259 void **file_baton)
261 ra_svn_baton_t *b = parent_baton;
262 const char *token = make_token('c', b->eb, pool);
264 SVN_ERR(check_for_error(b->eb, b->pool));
265 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "open-file", "ccc(?r)",
266 path, b->token, token, rev));
267 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
268 return SVN_NO_ERROR;
271 static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
272 apr_size_t *len)
274 ra_svn_baton_t *b = baton;
275 svn_string_t str;
277 SVN_ERR(check_for_error(b->eb, b->pool));
278 str.data = data;
279 str.len = *len;
280 return svn_ra_svn_write_cmd(b->conn, b->pool, "textdelta-chunk", "cs",
281 b->token, &str);
284 static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
286 ra_svn_baton_t *b = baton;
288 SVN_ERR(check_for_error(b->eb, b->pool));
289 SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "textdelta-end", "c",
290 b->token));
291 return SVN_NO_ERROR;
294 static svn_error_t *ra_svn_apply_textdelta(void *file_baton,
295 const char *base_checksum,
296 apr_pool_t *pool,
297 svn_txdelta_window_handler_t *wh,
298 void **wh_baton)
300 ra_svn_baton_t *b = file_baton;
301 svn_stream_t *diff_stream;
303 /* Tell the other side we're starting a text delta. */
304 SVN_ERR(check_for_error(b->eb, pool));
305 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "apply-textdelta", "c(?c)",
306 b->token, base_checksum));
308 /* Transform the window stream to an svndiff stream. Reuse the
309 * file baton for the stream handler, since it has all the
310 * needed information. */
311 diff_stream = svn_stream_create(b, pool);
312 svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
313 svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
314 if (svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_SVNDIFF1))
315 svn_txdelta_to_svndiff2(wh, wh_baton, diff_stream, 1, pool);
316 else
317 svn_txdelta_to_svndiff2(wh, wh_baton, diff_stream, 0, pool);
318 return SVN_NO_ERROR;
321 static svn_error_t *ra_svn_change_file_prop(void *file_baton,
322 const char *name,
323 const svn_string_t *value,
324 apr_pool_t *pool)
326 ra_svn_baton_t *b = file_baton;
328 SVN_ERR(check_for_error(b->eb, pool));
329 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "change-file-prop", "cc(?s)",
330 b->token, name, value));
331 return SVN_NO_ERROR;
334 static svn_error_t *ra_svn_close_file(void *file_baton,
335 const char *text_checksum,
336 apr_pool_t *pool)
338 ra_svn_baton_t *b = file_baton;
340 SVN_ERR(check_for_error(b->eb, pool));
341 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "close-file", "c(?c)",
342 b->token, text_checksum));
343 return SVN_NO_ERROR;
346 static svn_error_t *ra_svn_absent_file(const char *path,
347 void *parent_baton, apr_pool_t *pool)
349 ra_svn_baton_t *b = parent_baton;
351 /* Avoid sending an unknown command if the other end doesn't support
352 absent-file. */
353 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
354 return SVN_NO_ERROR;
356 SVN_ERR(check_for_error(b->eb, pool));
357 SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "absent-file", "cc", path,
358 b->token));
359 return SVN_NO_ERROR;
362 static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
364 ra_svn_edit_baton_t *eb = edit_baton;
365 svn_error_t *err;
367 assert(!eb->got_status);
368 eb->got_status = TRUE;
369 SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "close-edit", ""));
370 err = svn_ra_svn_read_cmd_response(eb->conn, pool, "");
371 if (err)
373 svn_error_clear(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", ""));
374 return err;
376 if (eb->callback)
377 SVN_ERR(eb->callback(eb->callback_baton));
378 return SVN_NO_ERROR;
381 static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
383 ra_svn_edit_baton_t *eb = edit_baton;
385 if (eb->got_status)
386 return SVN_NO_ERROR;
387 SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", ""));
388 SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, ""));
389 return SVN_NO_ERROR;
392 void svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
393 void **edit_baton, svn_ra_svn_conn_t *conn,
394 apr_pool_t *pool,
395 svn_ra_svn_edit_callback callback,
396 void *callback_baton)
398 svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool);
399 ra_svn_edit_baton_t *eb;
401 eb = apr_palloc(pool, sizeof(*eb));
402 eb->conn = conn;
403 eb->callback = callback;
404 eb->callback_baton = callback_baton;
405 eb->next_token = 0;
406 eb->got_status = FALSE;
408 ra_svn_editor->set_target_revision = ra_svn_target_rev;
409 ra_svn_editor->open_root = ra_svn_open_root;
410 ra_svn_editor->delete_entry = ra_svn_delete_entry;
411 ra_svn_editor->add_directory = ra_svn_add_dir;
412 ra_svn_editor->open_directory = ra_svn_open_dir;
413 ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
414 ra_svn_editor->close_directory = ra_svn_close_dir;
415 ra_svn_editor->absent_directory = ra_svn_absent_dir;
416 ra_svn_editor->add_file = ra_svn_add_file;
417 ra_svn_editor->open_file = ra_svn_open_file;
418 ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
419 ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
420 ra_svn_editor->close_file = ra_svn_close_file;
421 ra_svn_editor->absent_file = ra_svn_absent_file;
422 ra_svn_editor->close_edit = ra_svn_close_edit;
423 ra_svn_editor->abort_edit = ra_svn_abort_edit;
425 *editor = ra_svn_editor;
426 *edit_baton = eb;
429 /* --- DRIVING AN EDITOR --- */
431 /* Store a token entry. The token string will be copied into pool. */
432 static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
433 void *baton, const char *token,
434 svn_boolean_t is_file,
435 apr_pool_t *pool)
437 ra_svn_token_entry_t *entry;
439 entry = apr_palloc(pool, sizeof(*entry));
440 entry->token = apr_pstrdup(pool, token);
441 entry->baton = baton;
442 entry->is_file = is_file;
443 entry->dstream = NULL;
444 entry->pool = pool;
445 apr_hash_set(ds->tokens, entry->token, APR_HASH_KEY_STRING, entry);
446 return entry;
449 static svn_error_t *lookup_token(ra_svn_driver_state_t *ds, const char *token,
450 svn_boolean_t is_file,
451 ra_svn_token_entry_t **entry)
453 *entry = apr_hash_get(ds->tokens, token, APR_HASH_KEY_STRING);
454 if (!*entry || (*entry)->is_file != is_file)
455 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
456 _("Invalid file or dir token during edit"));
457 return SVN_NO_ERROR;
460 static svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
461 apr_pool_t *pool,
462 apr_array_header_t *params,
463 ra_svn_driver_state_t *ds)
465 svn_revnum_t rev;
467 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev));
468 SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
469 return SVN_NO_ERROR;
472 static svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
473 apr_pool_t *pool,
474 apr_array_header_t *params,
475 ra_svn_driver_state_t *ds)
477 svn_revnum_t rev;
478 apr_pool_t *subpool;
479 const char *token;
480 void *root_baton;
482 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)c", &rev, &token));
483 subpool = svn_pool_create(ds->pool);
484 SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
485 &root_baton));
486 store_token(ds, root_baton, token, FALSE, subpool);
487 return SVN_NO_ERROR;
490 static svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
491 apr_pool_t *pool,
492 apr_array_header_t *params,
493 ra_svn_driver_state_t *ds)
495 const char *path, *token;
496 svn_revnum_t rev;
497 ra_svn_token_entry_t *entry;
499 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)c", &path, &rev, &token));
500 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
501 path = svn_path_canonicalize(path, pool);
502 SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool));
503 return SVN_NO_ERROR;
506 static svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
507 apr_pool_t *pool,
508 apr_array_header_t *params,
509 ra_svn_driver_state_t *ds)
511 const char *path, *token, *child_token, *copy_path;
512 svn_revnum_t copy_rev;
513 ra_svn_token_entry_t *entry;
514 apr_pool_t *subpool;
515 void *child_baton;
517 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?cr)", &path, &token,
518 &child_token, &copy_path, &copy_rev));
519 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
520 subpool = svn_pool_create(entry->pool);
521 path = svn_path_canonicalize(path, pool);
522 if (copy_path)
523 copy_path = svn_path_canonicalize(copy_path, pool);
524 SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
525 copy_rev, subpool, &child_baton));
526 store_token(ds, child_baton, child_token, FALSE, subpool);
527 return SVN_NO_ERROR;
530 static svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
531 apr_pool_t *pool,
532 apr_array_header_t *params,
533 ra_svn_driver_state_t *ds)
535 const char *path, *token, *child_token;
536 svn_revnum_t rev;
537 ra_svn_token_entry_t *entry;
538 apr_pool_t *subpool;
539 void *child_baton;
541 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?r)", &path, &token,
542 &child_token, &rev));
543 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
544 subpool = svn_pool_create(entry->pool);
545 path = svn_path_canonicalize(path, pool);
546 SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
547 &child_baton));
548 store_token(ds, child_baton, child_token, FALSE, subpool);
549 return SVN_NO_ERROR;
552 static svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
553 apr_pool_t *pool,
554 apr_array_header_t *params,
555 ra_svn_driver_state_t *ds)
557 const char *token, *name;
558 svn_string_t *value;
559 ra_svn_token_entry_t *entry;
561 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc(?s)", &token, &name,
562 &value));
563 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
564 SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
565 entry->pool));
566 return SVN_NO_ERROR;
569 static svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
570 apr_pool_t *pool,
571 apr_array_header_t *params,
572 ra_svn_driver_state_t *ds)
574 const char *token;
575 ra_svn_token_entry_t *entry;
577 /* Parse and look up the directory token. */
578 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &token));
579 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
581 /* Close the directory and destroy the baton. */
582 SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
583 apr_hash_set(ds->tokens, token, APR_HASH_KEY_STRING, NULL);
584 svn_pool_destroy(entry->pool);
585 return SVN_NO_ERROR;
588 static svn_error_t *ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn,
589 apr_pool_t *pool,
590 apr_array_header_t *params,
591 ra_svn_driver_state_t *ds)
593 const char *path;
594 const char *token;
595 ra_svn_token_entry_t *entry;
597 /* Parse parameters and look up the directory token. */
598 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc", &path, &token));
599 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
601 /* Call the editor. */
602 SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool));
603 return SVN_NO_ERROR;
606 static svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
607 apr_pool_t *pool,
608 apr_array_header_t *params,
609 ra_svn_driver_state_t *ds)
611 const char *path, *token, *file_token, *copy_path;
612 svn_revnum_t copy_rev;
613 ra_svn_token_entry_t *entry, *file_entry;
615 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?cr)", &path, &token,
616 &file_token, &copy_path, &copy_rev));
617 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
618 ds->file_refs++;
619 path = svn_path_canonicalize(path, pool);
620 if (copy_path)
621 copy_path = svn_path_canonicalize(copy_path, pool);
622 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
623 SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev,
624 ds->file_pool, &file_entry->baton));
625 return SVN_NO_ERROR;
628 static svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
629 apr_pool_t *pool,
630 apr_array_header_t *params,
631 ra_svn_driver_state_t *ds)
633 const char *path, *token, *file_token;
634 svn_revnum_t rev;
635 ra_svn_token_entry_t *entry, *file_entry;
637 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?r)", &path, &token,
638 &file_token, &rev));
639 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
640 ds->file_refs++;
641 path = svn_path_canonicalize(path, pool);
642 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
643 SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool,
644 &file_entry->baton));
645 return SVN_NO_ERROR;
648 static svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
649 apr_pool_t *pool,
650 apr_array_header_t *params,
651 ra_svn_driver_state_t *ds)
653 const char *token;
654 ra_svn_token_entry_t *entry;
655 svn_txdelta_window_handler_t wh;
656 void *wh_baton;
657 char *base_checksum;
659 /* Parse arguments and look up the token. */
660 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)",
661 &token, &base_checksum));
662 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
663 if (entry->dstream)
664 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
665 _("Apply-textdelta already active"));
666 entry->pool = svn_pool_create(ds->file_pool);
667 SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
668 entry->pool, &wh, &wh_baton));
669 entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
670 return SVN_NO_ERROR;
673 static svn_error_t *ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn,
674 apr_pool_t *pool,
675 apr_array_header_t *params,
676 ra_svn_driver_state_t *ds)
678 const char *token;
679 ra_svn_token_entry_t *entry;
680 svn_string_t *str;
682 /* Parse arguments and look up the token. */
683 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cs", &token, &str));
684 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
685 if (!entry->dstream)
686 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
687 _("Apply-textdelta not active"));
688 SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
689 return SVN_NO_ERROR;
692 static svn_error_t *ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn,
693 apr_pool_t *pool,
694 apr_array_header_t *params,
695 ra_svn_driver_state_t *ds)
697 const char *token;
698 ra_svn_token_entry_t *entry;
700 /* Parse arguments and look up the token. */
701 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &token));
702 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
703 if (!entry->dstream)
704 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
705 _("Apply-textdelta not active"));
706 SVN_CMD_ERR(svn_stream_close(entry->dstream));
707 entry->dstream = NULL;
708 svn_pool_destroy(entry->pool);
709 return SVN_NO_ERROR;
712 static svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
713 apr_pool_t *pool,
714 apr_array_header_t *params,
715 ra_svn_driver_state_t *ds)
717 const char *token, *name;
718 svn_string_t *value;
719 ra_svn_token_entry_t *entry;
721 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc(?s)", &token, &name,
722 &value));
723 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
724 SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
725 return SVN_NO_ERROR;
728 static svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
729 apr_pool_t *pool,
730 apr_array_header_t *params,
731 ra_svn_driver_state_t *ds)
733 const char *token;
734 ra_svn_token_entry_t *entry;
735 const char *text_checksum;
737 /* Parse arguments and look up the file token. */
738 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)",
739 &token, &text_checksum));
740 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
742 /* Close the file and destroy the baton. */
743 SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
744 apr_hash_set(ds->tokens, token, APR_HASH_KEY_STRING, NULL);
745 if (--ds->file_refs == 0)
746 svn_pool_clear(ds->file_pool);
747 return SVN_NO_ERROR;
750 static svn_error_t *ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn,
751 apr_pool_t *pool,
752 apr_array_header_t *params,
753 ra_svn_driver_state_t *ds)
755 const char *path;
756 const char *token;
757 ra_svn_token_entry_t *entry;
759 /* Parse parameters and look up the parent directory token. */
760 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc", &path, &token));
761 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
763 /* Call the editor. */
764 SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool));
765 return SVN_NO_ERROR;
768 static svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
769 apr_pool_t *pool,
770 apr_array_header_t *params,
771 ra_svn_driver_state_t *ds)
773 SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
774 ds->done = TRUE;
775 if (ds->aborted)
776 *ds->aborted = FALSE;
777 return svn_ra_svn_write_cmd_response(conn, pool, "");
780 static svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
781 apr_pool_t *pool,
782 apr_array_header_t *params,
783 ra_svn_driver_state_t *ds)
785 ds->done = TRUE;
786 if (ds->aborted)
787 *ds->aborted = TRUE;
788 SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
789 return svn_ra_svn_write_cmd_response(conn, pool, "");
792 static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
793 apr_pool_t *pool,
794 apr_array_header_t *params,
795 ra_svn_driver_state_t *ds)
797 if (!ds->for_replay)
798 return svn_error_createf
799 (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
800 _("Command 'finish-replay' invalid outside of replays"));
801 ds->done = TRUE;
802 if (ds->aborted)
803 *ds->aborted = FALSE;
804 return SVN_NO_ERROR;
807 static const struct {
808 const char *cmd;
809 svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
810 apr_array_header_t *params,
811 ra_svn_driver_state_t *ds);
812 } ra_svn_edit_cmds[] = {
813 { "target-rev", ra_svn_handle_target_rev },
814 { "open-root", ra_svn_handle_open_root },
815 { "delete-entry", ra_svn_handle_delete_entry },
816 { "add-dir", ra_svn_handle_add_dir },
817 { "open-dir", ra_svn_handle_open_dir },
818 { "change-dir-prop", ra_svn_handle_change_dir_prop },
819 { "close-dir", ra_svn_handle_close_dir },
820 { "absent-dir", ra_svn_handle_absent_dir },
821 { "add-file", ra_svn_handle_add_file },
822 { "open-file", ra_svn_handle_open_file },
823 { "apply-textdelta", ra_svn_handle_apply_textdelta },
824 { "textdelta-chunk", ra_svn_handle_textdelta_chunk },
825 { "textdelta-end", ra_svn_handle_textdelta_end },
826 { "change-file-prop", ra_svn_handle_change_file_prop },
827 { "close-file", ra_svn_handle_close_file },
828 { "absent-file", ra_svn_handle_absent_file },
829 { "close-edit", ra_svn_handle_close_edit },
830 { "abort-edit", ra_svn_handle_abort_edit },
831 { "finish-replay", ra_svn_handle_finish_replay },
832 { NULL }
835 static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
836 void *baton)
838 ra_svn_driver_state_t *ds = baton;
839 const char *cmd;
840 apr_array_header_t *params;
842 /* We blocked trying to send an error. Read and discard an editing
843 * command in order to avoid deadlock. */
844 SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "wl", &cmd, &params));
845 if (strcmp(cmd, "abort-edit") == 0)
847 ds->done = TRUE;
848 svn_ra_svn__set_block_handler(conn, NULL, NULL);
850 return SVN_NO_ERROR;
853 svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
854 apr_pool_t *pool,
855 const svn_delta_editor_t *editor,
856 void *edit_baton,
857 svn_boolean_t *aborted,
858 svn_boolean_t for_replay)
860 ra_svn_driver_state_t state;
861 apr_pool_t *subpool = svn_pool_create(pool);
862 const char *cmd;
863 int i;
864 svn_error_t *err, *write_err;
865 apr_array_header_t *params;
867 state.editor = editor;
868 state.edit_baton = edit_baton;
869 state.tokens = apr_hash_make(pool);
870 state.aborted = aborted;
871 state.done = FALSE;
872 state.pool = pool;
873 state.file_pool = svn_pool_create(pool);
874 state.file_refs = 0;
875 state.for_replay = for_replay;
877 while (!state.done)
879 svn_pool_clear(subpool);
880 SVN_ERR(svn_ra_svn_read_tuple(conn, subpool, "wl", &cmd, &params));
881 for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
883 if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0)
884 break;
886 if (ra_svn_edit_cmds[i].cmd)
887 err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state);
888 else
890 err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
891 _("Unknown command '%s'"), cmd);
892 err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
895 if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
897 if (aborted)
898 *aborted = TRUE;
899 if (!state.done)
901 /* Abort the edit and use non-blocking I/O to write the error. */
902 svn_error_clear(editor->abort_edit(edit_baton, subpool));
903 svn_ra_svn__set_block_handler(conn, blocked_write, &state);
905 write_err = svn_ra_svn_write_cmd_failure(conn, subpool, err->child);
906 if (!write_err)
907 write_err = svn_ra_svn_flush(conn, subpool);
908 svn_ra_svn__set_block_handler(conn, NULL, NULL);
909 svn_error_clear(err);
910 SVN_ERR(write_err);
911 break;
913 SVN_ERR(err);
916 /* Read and discard editing commands until the edit is complete. */
917 while (!state.done)
919 svn_pool_clear(subpool);
920 SVN_ERR(svn_ra_svn_read_tuple(conn, subpool, "wl", &cmd, &params));
921 state.done = (strcmp(cmd, "abort-edit") == 0);
924 svn_pool_destroy(subpool);
925 return SVN_NO_ERROR;
928 svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
929 const svn_delta_editor_t *editor,
930 void *edit_baton,
931 svn_boolean_t *aborted)
933 return svn_ra_svn_drive_editor2(conn,
934 pool,
935 editor,
936 edit_baton,
937 aborted,
938 FALSE);