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
23 #include <apr_general.h>
24 #include <apr_strings.h>
29 #include "svn_types.h"
30 #include "svn_string.h"
31 #include "svn_error.h"
33 #include "svn_delta.h"
34 #include "svn_ra_svn.h"
35 #include "svn_pools.h"
36 #include "svn_private_config.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.
50 svn_ra_svn_conn_t
*conn
;
51 svn_ra_svn_edit_callback callback
; /* Called on successful completion. */
54 svn_boolean_t got_status
;
55 } ra_svn_edit_baton_t
;
57 /* Works for both directories and files. */
59 svn_ra_svn_conn_t
*conn
;
61 ra_svn_edit_baton_t
*eb
;
66 const svn_delta_editor_t
*editor
;
69 svn_boolean_t
*aborted
;
72 apr_pool_t
*file_pool
;
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. */
89 svn_boolean_t is_file
;
90 svn_stream_t
*dstream
; /* svndiff stream for apply_textdelta */
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
,
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
,
104 ra_svn_edit_baton_t
*eb
,
109 b
= apr_palloc(pool
, sizeof(*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"));
134 static svn_error_t
*ra_svn_target_rev(void *edit_baton
, svn_revnum_t rev
,
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
));
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
,
153 *root_baton
= ra_svn_make_baton(eb
->conn
, pool
, eb
, token
);
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
));
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
);
185 static svn_error_t
*ra_svn_open_dir(const char *path
, void *parent_baton
,
186 svn_revnum_t rev
, apr_pool_t
*pool
,
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
);
199 static svn_error_t
*ra_svn_change_dir_prop(void *dir_baton
, const char *name
,
200 const svn_string_t
*value
,
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
));
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
));
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
227 if (! svn_ra_svn_has_capability(b
->conn
, SVN_RA_SVN_CAP_ABSENT_ENTRIES
))
230 SVN_ERR(check_for_error(b
->eb
, pool
));
231 SVN_ERR(svn_ra_svn_write_cmd(b
->conn
, pool
, "absent-dir", "cc", path
,
236 static svn_error_t
*ra_svn_add_file(const char *path
,
238 const char *copy_path
,
239 svn_revnum_t copy_rev
,
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
);
255 static svn_error_t
*ra_svn_open_file(const char *path
,
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
);
271 static svn_error_t
*ra_svn_svndiff_handler(void *baton
, const char *data
,
274 ra_svn_baton_t
*b
= baton
;
277 SVN_ERR(check_for_error(b
->eb
, b
->pool
));
280 return svn_ra_svn_write_cmd(b
->conn
, b
->pool
, "textdelta-chunk", "cs",
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",
294 static svn_error_t
*ra_svn_apply_textdelta(void *file_baton
,
295 const char *base_checksum
,
297 svn_txdelta_window_handler_t
*wh
,
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
);
317 svn_txdelta_to_svndiff2(wh
, wh_baton
, diff_stream
, 0, pool
);
321 static svn_error_t
*ra_svn_change_file_prop(void *file_baton
,
323 const svn_string_t
*value
,
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
));
334 static svn_error_t
*ra_svn_close_file(void *file_baton
,
335 const char *text_checksum
,
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
));
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
353 if (! svn_ra_svn_has_capability(b
->conn
, SVN_RA_SVN_CAP_ABSENT_ENTRIES
))
356 SVN_ERR(check_for_error(b
->eb
, pool
));
357 SVN_ERR(svn_ra_svn_write_cmd(b
->conn
, pool
, "absent-file", "cc", path
,
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
;
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
, "");
373 svn_error_clear(svn_ra_svn_write_cmd(eb
->conn
, pool
, "abort-edit", ""));
377 SVN_ERR(eb
->callback(eb
->callback_baton
));
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
;
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
, ""));
392 void svn_ra_svn_get_editor(const svn_delta_editor_t
**editor
,
393 void **edit_baton
, svn_ra_svn_conn_t
*conn
,
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
));
403 eb
->callback
= callback
;
404 eb
->callback_baton
= callback_baton
;
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
;
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
,
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
;
445 apr_hash_set(ds
->tokens
, entry
->token
, APR_HASH_KEY_STRING
, 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"));
460 static svn_error_t
*ra_svn_handle_target_rev(svn_ra_svn_conn_t
*conn
,
462 apr_array_header_t
*params
,
463 ra_svn_driver_state_t
*ds
)
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
));
472 static svn_error_t
*ra_svn_handle_open_root(svn_ra_svn_conn_t
*conn
,
474 apr_array_header_t
*params
,
475 ra_svn_driver_state_t
*ds
)
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
,
486 store_token(ds
, root_baton
, token
, FALSE
, subpool
);
490 static svn_error_t
*ra_svn_handle_delete_entry(svn_ra_svn_conn_t
*conn
,
492 apr_array_header_t
*params
,
493 ra_svn_driver_state_t
*ds
)
495 const char *path
, *token
;
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
));
506 static svn_error_t
*ra_svn_handle_add_dir(svn_ra_svn_conn_t
*conn
,
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
;
517 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "ccc(?cr)", &path
, &token
,
518 &child_token
, ©_path
, ©_rev
));
519 SVN_ERR(lookup_token(ds
, token
, FALSE
, &entry
));
520 subpool
= svn_pool_create(entry
->pool
);
521 path
= svn_path_canonicalize(path
, pool
);
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
);
530 static svn_error_t
*ra_svn_handle_open_dir(svn_ra_svn_conn_t
*conn
,
532 apr_array_header_t
*params
,
533 ra_svn_driver_state_t
*ds
)
535 const char *path
, *token
, *child_token
;
537 ra_svn_token_entry_t
*entry
;
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
,
548 store_token(ds
, child_baton
, child_token
, FALSE
, subpool
);
552 static svn_error_t
*ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t
*conn
,
554 apr_array_header_t
*params
,
555 ra_svn_driver_state_t
*ds
)
557 const char *token
, *name
;
559 ra_svn_token_entry_t
*entry
;
561 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "cc(?s)", &token
, &name
,
563 SVN_ERR(lookup_token(ds
, token
, FALSE
, &entry
));
564 SVN_CMD_ERR(ds
->editor
->change_dir_prop(entry
->baton
, name
, value
,
569 static svn_error_t
*ra_svn_handle_close_dir(svn_ra_svn_conn_t
*conn
,
571 apr_array_header_t
*params
,
572 ra_svn_driver_state_t
*ds
)
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
);
588 static svn_error_t
*ra_svn_handle_absent_dir(svn_ra_svn_conn_t
*conn
,
590 apr_array_header_t
*params
,
591 ra_svn_driver_state_t
*ds
)
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
));
606 static svn_error_t
*ra_svn_handle_add_file(svn_ra_svn_conn_t
*conn
,
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
, ©_path
, ©_rev
));
617 SVN_ERR(lookup_token(ds
, token
, FALSE
, &entry
));
619 path
= svn_path_canonicalize(path
, pool
);
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
));
628 static svn_error_t
*ra_svn_handle_open_file(svn_ra_svn_conn_t
*conn
,
630 apr_array_header_t
*params
,
631 ra_svn_driver_state_t
*ds
)
633 const char *path
, *token
, *file_token
;
635 ra_svn_token_entry_t
*entry
, *file_entry
;
637 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "ccc(?r)", &path
, &token
,
639 SVN_ERR(lookup_token(ds
, token
, FALSE
, &entry
));
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
));
648 static svn_error_t
*ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t
*conn
,
650 apr_array_header_t
*params
,
651 ra_svn_driver_state_t
*ds
)
654 ra_svn_token_entry_t
*entry
;
655 svn_txdelta_window_handler_t wh
;
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
));
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
);
673 static svn_error_t
*ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t
*conn
,
675 apr_array_header_t
*params
,
676 ra_svn_driver_state_t
*ds
)
679 ra_svn_token_entry_t
*entry
;
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
));
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
));
692 static svn_error_t
*ra_svn_handle_textdelta_end(svn_ra_svn_conn_t
*conn
,
694 apr_array_header_t
*params
,
695 ra_svn_driver_state_t
*ds
)
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
));
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
);
712 static svn_error_t
*ra_svn_handle_change_file_prop(svn_ra_svn_conn_t
*conn
,
714 apr_array_header_t
*params
,
715 ra_svn_driver_state_t
*ds
)
717 const char *token
, *name
;
719 ra_svn_token_entry_t
*entry
;
721 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "cc(?s)", &token
, &name
,
723 SVN_ERR(lookup_token(ds
, token
, TRUE
, &entry
));
724 SVN_CMD_ERR(ds
->editor
->change_file_prop(entry
->baton
, name
, value
, pool
));
728 static svn_error_t
*ra_svn_handle_close_file(svn_ra_svn_conn_t
*conn
,
730 apr_array_header_t
*params
,
731 ra_svn_driver_state_t
*ds
)
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
);
750 static svn_error_t
*ra_svn_handle_absent_file(svn_ra_svn_conn_t
*conn
,
752 apr_array_header_t
*params
,
753 ra_svn_driver_state_t
*ds
)
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
));
768 static svn_error_t
*ra_svn_handle_close_edit(svn_ra_svn_conn_t
*conn
,
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
));
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
,
782 apr_array_header_t
*params
,
783 ra_svn_driver_state_t
*ds
)
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
,
794 apr_array_header_t
*params
,
795 ra_svn_driver_state_t
*ds
)
798 return svn_error_createf
799 (SVN_ERR_RA_SVN_UNKNOWN_CMD
, NULL
,
800 _("Command 'finish-replay' invalid outside of replays"));
803 *ds
->aborted
= FALSE
;
807 static const struct {
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
},
835 static svn_error_t
*blocked_write(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
838 ra_svn_driver_state_t
*ds
= baton
;
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
, ¶ms
));
845 if (strcmp(cmd
, "abort-edit") == 0)
848 svn_ra_svn__set_block_handler(conn
, NULL
, NULL
);
853 svn_error_t
*svn_ra_svn_drive_editor2(svn_ra_svn_conn_t
*conn
,
855 const svn_delta_editor_t
*editor
,
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
);
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
;
873 state
.file_pool
= svn_pool_create(pool
);
875 state
.for_replay
= for_replay
;
879 svn_pool_clear(subpool
);
880 SVN_ERR(svn_ra_svn_read_tuple(conn
, subpool
, "wl", &cmd
, ¶ms
));
881 for (i
= 0; ra_svn_edit_cmds
[i
].cmd
; i
++)
883 if (strcmp(cmd
, ra_svn_edit_cmds
[i
].cmd
) == 0)
886 if (ra_svn_edit_cmds
[i
].cmd
)
887 err
= (*ra_svn_edit_cmds
[i
].handler
)(conn
, subpool
, params
, &state
);
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
)
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
);
907 write_err
= svn_ra_svn_flush(conn
, subpool
);
908 svn_ra_svn__set_block_handler(conn
, NULL
, NULL
);
909 svn_error_clear(err
);
916 /* Read and discard editing commands until the edit is complete. */
919 svn_pool_clear(subpool
);
920 SVN_ERR(svn_ra_svn_read_tuple(conn
, subpool
, "wl", &cmd
, ¶ms
));
921 state
.done
= (strcmp(cmd
, "abort-edit") == 0);
924 svn_pool_destroy(subpool
);
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
,
931 svn_boolean_t
*aborted
)
933 return svn_ra_svn_drive_editor2(conn
,