2
* editor.c : Driving and consuming an editor across an svn connection
4
* ====================================================================
5
* Copyright (c) 2000-2004 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
} ra_svn_edit_baton_t;
56
/* Works for both directories and files. */
58
svn_ra_svn_conn_t *conn;
60
ra_svn_edit_baton_t *eb;
65
const svn_delta_editor_t *editor;
68
svn_boolean_t *aborted;
70
} ra_svn_driver_state_t;
75
svn_error_t *err; /* Tracks delayed errors. */
77
} ra_svn_token_entry_t;
79
/* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */
81
static const char *make_token(char type, ra_svn_edit_baton_t *eb,
84
return apr_psprintf(pool, "%c%d", type, eb->next_token++);
87
static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn,
89
ra_svn_edit_baton_t *eb,
94
b = apr_palloc(pool, sizeof(*b));
102
static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
105
ra_svn_edit_baton_t *eb = edit_baton;
107
SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "target-rev", "r", rev));
108
SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, ""));
112
static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
113
apr_pool_t *pool, void **root_baton)
115
ra_svn_edit_baton_t *eb = edit_baton;
116
const char *token = make_token('d', eb, pool);
118
SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "open-root", "(?r)c", rev,
120
SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, ""));
121
*root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
125
static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
126
void *parent_baton, apr_pool_t *pool)
128
ra_svn_baton_t *b = parent_baton;
130
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "delete-entry", "c(?r)c",
131
path, rev, b->token));
132
SVN_ERR(svn_ra_svn_read_cmd_response(b->conn, pool, ""));
136
static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
137
const char *copy_path,
138
svn_revnum_t copy_rev,
139
apr_pool_t *pool, void **child_baton)
141
ra_svn_baton_t *b = parent_baton;
142
const char *token = make_token('d', b->eb, pool);
144
assert((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
145
|| (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
146
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "add-dir", "ccc(?cr)", path,
147
b->token, token, copy_path, copy_rev));
148
SVN_ERR(svn_ra_svn_read_cmd_response(b->conn, pool, ""));
149
*child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
153
static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
154
svn_revnum_t rev, apr_pool_t *pool,
157
ra_svn_baton_t *b = parent_baton;
158
const char *token = make_token('d', b->eb, pool);
160
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "open-dir", "ccc(?r)",
161
path, b->token, token, rev));
162
SVN_ERR(svn_ra_svn_read_cmd_response(b->conn, pool, ""));
163
*child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
167
static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
168
const svn_string_t *value,
171
ra_svn_baton_t *b = dir_baton;
173
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "change-dir-prop", "cc(?s)",
174
b->token, name, value));
178
static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
180
ra_svn_baton_t *b = dir_baton;
182
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "close-dir", "c", b->token));
183
SVN_ERR(svn_ra_svn_read_cmd_response(b->conn, pool, ""));
187
static svn_error_t *ra_svn_add_file(const char *path,
189
const char *copy_path,
190
svn_revnum_t copy_rev,
194
ra_svn_baton_t *b = parent_baton;
195
const char *token = make_token('c', b->eb, pool);
197
assert((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
198
|| (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
199
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "add-file", "ccc(?cr)", path,
200
b->token, token, copy_path, copy_rev));
201
*file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
205
static svn_error_t *ra_svn_open_file(const char *path,
211
ra_svn_baton_t *b = parent_baton;
212
const char *token = make_token('c', b->eb, pool);
214
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "open-file", "ccc(?r)",
215
path, b->token, token, rev));
216
*file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
220
static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
223
ra_svn_baton_t *b = baton;
228
return svn_ra_svn_write_string(b->conn, b->pool, &str);
231
static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
233
ra_svn_baton_t *b = baton;
235
SVN_ERR(svn_ra_svn_write_cstring(b->conn, b->pool, ""));
239
static svn_error_t *ra_svn_apply_textdelta(void *file_baton,
240
const char *base_checksum,
242
svn_txdelta_window_handler_t *wh,
245
ra_svn_baton_t *b = file_baton;
246
svn_stream_t *diff_stream;
248
/* Tell the other side we're starting a text delta. */
249
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "apply-textdelta", "c(?c)",
250
b->token, base_checksum));
252
/* Transform the window stream to an svndiff stream. Reuse the
253
* file baton for the stream handler, since it has all the
254
* needed information. */
255
diff_stream = svn_stream_create(b, pool);
256
svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
257
svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
258
svn_txdelta_to_svndiff(diff_stream, pool, wh, wh_baton);
262
static svn_error_t *ra_svn_change_file_prop(void *file_baton,
264
const svn_string_t *value,
267
ra_svn_baton_t *b = file_baton;
269
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "change-file-prop", "cc(?s)",
270
b->token, name, value));
274
static svn_error_t *ra_svn_close_file(void *file_baton,
275
const char *text_checksum,
278
ra_svn_baton_t *b = file_baton;
280
SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "close-file", "c(?c)",
281
b->token, text_checksum));
282
SVN_ERR(svn_ra_svn_read_cmd_response(b->conn, pool, ""));
286
static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
288
ra_svn_edit_baton_t *eb = edit_baton;
290
SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "close-edit", ""));
291
SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, ""));
293
SVN_ERR(eb->callback(eb->callback_baton));
297
static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
299
ra_svn_edit_baton_t *eb = edit_baton;
301
SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", ""));
302
SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, ""));
306
void svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
307
void **edit_baton, svn_ra_svn_conn_t *conn,
308
apr_pool_t *pool, svn_ra_svn_edit_callback callback,
309
void *callback_baton)
311
svn_delta_editor_t *ra_svn_editor;
312
ra_svn_edit_baton_t *eb;
314
if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
316
svn_ra_svn__get_editorp(editor, edit_baton, conn, pool, callback,
321
eb = apr_palloc(pool, sizeof(*eb));
323
eb->callback = callback;
324
eb->callback_baton = callback_baton;
327
ra_svn_editor = svn_delta_default_editor(pool);
328
ra_svn_editor->set_target_revision = ra_svn_target_rev;
329
ra_svn_editor->open_root = ra_svn_open_root;
330
ra_svn_editor->delete_entry = ra_svn_delete_entry;
331
ra_svn_editor->add_directory = ra_svn_add_dir;
332
ra_svn_editor->open_directory = ra_svn_open_dir;
333
ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
334
ra_svn_editor->close_directory = ra_svn_close_dir;
335
ra_svn_editor->add_file = ra_svn_add_file;
336
ra_svn_editor->open_file = ra_svn_open_file;
337
ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
338
ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
339
ra_svn_editor->close_file = ra_svn_close_file;
340
ra_svn_editor->close_edit = ra_svn_close_edit;
341
ra_svn_editor->abort_edit = ra_svn_abort_edit;
343
*editor = ra_svn_editor;
347
/* --- DRIVING AN EDITOR --- */
349
static apr_status_t clear_token_err(void *arg)
351
ra_svn_token_entry_t *entry = arg;
353
svn_error_clear(entry->err);
357
/* Store a token entry. The token string will be copied into pool. */
358
static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
359
void *baton, const char *token,
362
ra_svn_token_entry_t *entry;
364
entry = apr_palloc(pool, sizeof(*entry));
365
entry->token = apr_pstrdup(pool, token);
366
entry->baton = baton;
369
apr_hash_set(ds->tokens, entry->token, APR_HASH_KEY_STRING, entry);
370
apr_pool_cleanup_register(pool, entry, clear_token_err,
371
apr_pool_cleanup_null);
375
static svn_error_t *lookup_token(ra_svn_driver_state_t *ds, const char *token,
376
ra_svn_token_entry_t **entry,
380
*entry = apr_hash_get(ds->tokens, token, APR_HASH_KEY_STRING);
382
return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
383
_("Invalid file or dir token during edit"));
387
static svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
389
apr_array_header_t *params,
392
ra_svn_driver_state_t *ds = baton;
395
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev));
396
SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
397
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
401
static svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
403
apr_array_header_t *params,
406
ra_svn_driver_state_t *ds = baton;
412
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)c", &rev, &token));
413
subpool = svn_pool_create(ds->pool);
414
SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
416
store_token(ds, root_baton, token, subpool);
417
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
421
static svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
423
apr_array_header_t *params,
426
ra_svn_driver_state_t *ds = baton;
427
const char *path, *token;
429
ra_svn_token_entry_t *entry;
431
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)c", &path, &rev, &token));
432
SVN_ERR(lookup_token(ds, token, &entry, pool));
433
path = svn_path_canonicalize(path, pool);
434
SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, entry->pool));
435
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
439
static svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
441
apr_array_header_t *params,
444
ra_svn_driver_state_t *ds = baton;
445
const char *path, *token, *child_token, *copy_path;
446
svn_revnum_t copy_rev;
447
ra_svn_token_entry_t *entry;
451
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?cr)", &path, &token,
452
&child_token, ©_path, ©_rev));
453
SVN_ERR(lookup_token(ds, token, &entry, pool));
454
subpool = svn_pool_create(entry->pool);
455
path = svn_path_canonicalize(path, pool);
457
copy_path = svn_path_canonicalize(copy_path, pool);
458
SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
459
copy_rev, subpool, &child_baton));
460
store_token(ds, child_baton, child_token, subpool);
461
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
465
static svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
467
apr_array_header_t *params,
470
ra_svn_driver_state_t *ds = baton;
471
const char *path, *token, *child_token;
473
ra_svn_token_entry_t *entry;
477
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?r)", &path, &token,
478
&child_token, &rev));
479
SVN_ERR(lookup_token(ds, token, &entry, pool));
480
subpool = svn_pool_create(entry->pool);
481
path = svn_path_canonicalize(path, pool);
482
SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
484
store_token(ds, child_baton, child_token, subpool);
485
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
489
static svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
491
apr_array_header_t *params,
494
ra_svn_driver_state_t *ds = baton;
495
const char *token, *name;
497
ra_svn_token_entry_t *entry;
499
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc(?s)", &token, &name,
501
SVN_ERR(lookup_token(ds, token, &entry, pool));
503
entry->err = ds->editor->change_dir_prop(entry->baton, name, value,
508
static svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
510
apr_array_header_t *params,
513
ra_svn_driver_state_t *ds = baton;
515
ra_svn_token_entry_t *entry;
517
/* Parse and look up the directory token. */
518
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &token));
519
SVN_ERR(lookup_token(ds, token, &entry, pool));
521
/* Return any delayed errors. */
522
apr_pool_cleanup_kill(entry->pool, entry, clear_token_err);
523
SVN_CMD_ERR(entry->err);
525
/* Close the directory and destroy the baton. */
526
SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
527
apr_hash_set(ds->tokens, token, APR_HASH_KEY_STRING, NULL);
528
apr_pool_destroy(entry->pool);
529
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
533
static svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
535
apr_array_header_t *params,
538
ra_svn_driver_state_t *ds = baton;
539
const char *path, *token, *file_token, *copy_path;
540
svn_revnum_t copy_rev;
541
ra_svn_token_entry_t *entry, *file_entry;
544
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?cr)", &path, &token,
545
&file_token, ©_path, ©_rev));
546
SVN_ERR(lookup_token(ds, token, &entry, pool));
548
/* File may outlive parent directory, so use ds->pool here. */
549
subpool = svn_pool_create(ds->pool);
550
path = svn_path_canonicalize(path, pool);
552
copy_path = svn_path_canonicalize(copy_path, pool);
553
file_entry = store_token(ds, NULL, file_token, subpool);
554
file_entry->err = ds->editor->add_file(path, entry->baton, copy_path,
560
static svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
562
apr_array_header_t *params,
565
ra_svn_driver_state_t *ds = baton;
566
const char *path, *token, *file_token;
568
ra_svn_token_entry_t *entry, *file_entry;
571
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccc(?r)", &path, &token,
573
SVN_ERR(lookup_token(ds, token, &entry, pool));
575
/* File may outlive parent directory, so use ds->pool here. */
576
subpool = svn_pool_create(ds->pool);
577
path = svn_path_canonicalize(path, pool);
578
file_entry = store_token(ds, NULL, file_token, subpool);
579
file_entry->err = ds->editor->open_file(path, entry->baton, rev, subpool,
584
static svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
586
apr_array_header_t *params,
589
ra_svn_driver_state_t *ds = baton;
591
ra_svn_token_entry_t *entry;
592
svn_txdelta_window_handler_t wh;
594
svn_stream_t *stream;
596
svn_ra_svn_item_t *item;
599
/* Parse arguments and look up the token. */
600
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)",
601
&token, &base_checksum));
602
SVN_ERR(lookup_token(ds, token, &entry, pool));
605
entry->err = ds->editor->apply_textdelta(entry->baton, base_checksum, pool,
608
stream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
609
subpool = svn_pool_create(entry->pool);
612
apr_pool_clear(subpool);
613
SVN_ERR(svn_ra_svn_read_item(conn, subpool, &item));
614
if (item->kind != SVN_RA_SVN_STRING)
615
return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
616
_("Non-string as part of text delta"));
617
if (item->u.string->len == 0)
620
entry->err = svn_stream_write(stream, item->u.string->data,
621
&item->u.string->len);
624
entry->err = svn_stream_close(stream);
625
apr_pool_destroy(subpool);
629
static svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
631
apr_array_header_t *params,
634
ra_svn_driver_state_t *ds = baton;
635
const char *token, *name;
637
ra_svn_token_entry_t *entry;
639
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cc(?s)", &token, &name,
641
SVN_ERR(lookup_token(ds, token, &entry, pool));
643
entry->err = ds->editor->change_file_prop(entry->baton, name, value,
648
static svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
650
apr_array_header_t *params,
653
ra_svn_driver_state_t *ds = baton;
655
ra_svn_token_entry_t *entry;
656
const char *text_checksum;
658
/* Parse arguments and look up the file token. */
659
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)",
660
&token, &text_checksum));
661
SVN_ERR(lookup_token(ds, token, &entry, pool));
663
/* Return any delayed errors. */
664
apr_pool_cleanup_kill(entry->pool, entry, clear_token_err);
665
SVN_CMD_ERR(entry->err);
667
/* Close the file and destroy the baton. */
668
SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
669
apr_hash_set(ds->tokens, token, APR_HASH_KEY_STRING, NULL);
670
apr_pool_destroy(entry->pool);
671
SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
675
static svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
677
apr_array_header_t *params,
680
ra_svn_driver_state_t *ds = baton;
683
err = ds->editor->close_edit(ds->edit_baton, pool);
685
*ds->aborted = (err != SVN_NO_ERROR);
687
return svn_ra_svn_write_cmd_response(conn, pool, "");
690
static svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
692
apr_array_header_t *params,
695
ra_svn_driver_state_t *ds = baton;
699
SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
700
return svn_ra_svn_write_cmd_response(conn, pool, "");
703
static const svn_ra_svn_cmd_entry_t ra_svn_edit_commands[] = {
704
{ "target-rev", ra_svn_handle_target_rev },
705
{ "open-root", ra_svn_handle_open_root },
706
{ "delete-entry", ra_svn_handle_delete_entry },
707
{ "add-dir", ra_svn_handle_add_dir },
708
{ "open-dir", ra_svn_handle_open_dir },
709
{ "change-dir-prop", ra_svn_handle_change_dir_prop },
710
{ "close-dir", ra_svn_handle_close_dir },
711
{ "add-file", ra_svn_handle_add_file },
712
{ "open-file", ra_svn_handle_open_file },
713
{ "apply-textdelta", ra_svn_handle_apply_textdelta },
714
{ "change-file-prop", ra_svn_handle_change_file_prop },
715
{ "close-file", ra_svn_handle_close_file },
716
{ "close-edit", ra_svn_handle_close_edit, TRUE },
717
{ "abort-edit", ra_svn_handle_abort_edit, TRUE },
721
svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
722
const svn_delta_editor_t *editor,
724
svn_boolean_t *aborted)
726
ra_svn_driver_state_t state;
728
if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
729
return svn_ra_svn__drive_editorp(conn, pool, editor, edit_baton, aborted);
731
state.editor = editor;
732
state.edit_baton = edit_baton;
733
state.tokens = apr_hash_make(pool);
734
state.aborted = aborted;
736
return svn_ra_svn_handle_commands(conn, pool, ra_svn_edit_commands, &state);