editorp.c revision 362181
1/*
2 * editorp.c :  Driving and consuming an editor across an svn connection
3 *
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
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#define APR_WANT_STRFUNC
27#include <apr_want.h>
28#include <apr_general.h>
29#include <apr_strings.h>
30
31#include "svn_hash.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"
38#include "svn_path.h"
39#include "svn_pools.h"
40#include "svn_private_config.h"
41
42#include "private/svn_atomic.h"
43#include "private/svn_fspath.h"
44#include "private/svn_editor.h"
45#include "private/svn_string_private.h"
46#include "private/svn_subr_private.h"
47
48#include "ra_svn.h"
49
50/*
51 * Both the client and server in the svn protocol need to drive and
52 * consume editors.  For a commit, the client drives and the server
53 * consumes; for an update/switch/status/diff, the server drives and
54 * the client consumes.  This file provides a generic framework for
55 * marshalling and unmarshalling editor operations over an svn
56 * connection; both ends are useful for both server and client.
57 */
58
59typedef struct ra_svn_edit_baton_t {
60  svn_ra_svn_conn_t *conn;
61  svn_ra_svn_edit_callback callback;    /* Called on successful completion. */
62  void *callback_baton;
63  apr_uint64_t next_token;
64  svn_boolean_t got_status;
65} ra_svn_edit_baton_t;
66
67/* Works for both directories and files. */
68typedef struct ra_svn_baton_t {
69  svn_ra_svn_conn_t *conn;
70  apr_pool_t *pool;
71  ra_svn_edit_baton_t *eb;
72  svn_string_t *token;
73} ra_svn_baton_t;
74
75/* Forward declaration. */
76typedef struct ra_svn_token_entry_t ra_svn_token_entry_t;
77
78typedef struct ra_svn_driver_state_t {
79  const svn_delta_editor_t *editor;
80  void *edit_baton;
81  apr_hash_t *tokens;
82
83  /* Entry for the last token seen.  May be NULL. */
84  ra_svn_token_entry_t *last_token;
85  svn_boolean_t *aborted;
86  svn_boolean_t done;
87  apr_pool_t *pool;
88  apr_pool_t *file_pool;
89  int file_refs;
90  svn_boolean_t for_replay;
91} ra_svn_driver_state_t;
92
93/* Works for both directories and files; however, the pool handling is
94   different for files.  To save space during commits (where file
95   batons generally last until the end of the commit), token entries
96   for files are all created in a single reference-counted pool (the
97   file_pool member of the driver state structure), which is cleared
98   at close_file time when the reference count hits zero.  So the pool
99   field in this structure is vestigial for files, and we use it for a
100   different purpose instead: at apply-textdelta time, we set it to a
101   subpool of the file pool, which is destroyed in textdelta-end. */
102struct ra_svn_token_entry_t {
103  svn_string_t *token;
104  void *baton;
105  svn_boolean_t is_file;
106  svn_stream_t *dstream;  /* svndiff stream for apply_textdelta */
107  apr_pool_t *pool;
108};
109
110/* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */
111
112static svn_string_t *
113make_token(char type,
114           ra_svn_edit_baton_t *eb,
115           apr_pool_t *pool)
116{
117  apr_size_t len;
118  char buffer[1 + SVN_INT64_BUFFER_SIZE];
119  buffer[0] = type;
120  len = 1 + svn__ui64toa(&buffer[1], eb->next_token++);
121
122  return svn_string_ncreate(buffer, len, pool);
123}
124
125static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn,
126                                         apr_pool_t *pool,
127                                         ra_svn_edit_baton_t *eb,
128                                         svn_string_t *token)
129{
130  ra_svn_baton_t *b;
131
132  b = apr_palloc(pool, sizeof(*b));
133  b->conn = conn;
134  b->pool = pool;
135  b->eb = eb;
136  b->token = token;
137  return b;
138}
139
140/* Check for an early error status report from the consumer.  If we
141 * get one, abort the edit and return the error. */
142static svn_error_t *
143check_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
144{
145  svn_boolean_t available;
146  SVN_ERR_ASSERT(!eb->got_status);
147
148  /* reset TX counter */
149  eb->conn->written_since_error_check = 0;
150
151  /* if we weren't asked to always check, wait for at least the next TX */
152  eb->conn->may_check_for_error = eb->conn->error_check_interval == 0;
153
154  /* any incoming data? */
155  SVN_ERR(svn_ra_svn__data_available(eb->conn, &available));
156  if (available)
157    {
158      eb->got_status = TRUE;
159      SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
160      SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
161      /* We shouldn't get here if the consumer is doing its job. */
162      return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
163                              _("Successful edit status returned too soon"));
164    }
165  return SVN_NO_ERROR;
166}
167
168static svn_error_t *
169check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
170{
171  return eb->conn->may_check_for_error
172    ? check_for_error_internal(eb, pool)
173    : SVN_NO_ERROR;
174}
175
176static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
177                                      apr_pool_t *pool)
178{
179  ra_svn_edit_baton_t *eb = edit_baton;
180
181  SVN_ERR(check_for_error(eb, pool));
182  SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev));
183  return SVN_NO_ERROR;
184}
185
186static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
187                                     apr_pool_t *pool, void **root_baton)
188{
189  ra_svn_edit_baton_t *eb = edit_baton;
190  svn_string_t *token = make_token('d', eb, pool);
191
192  SVN_ERR(check_for_error(eb, pool));
193  SVN_ERR(svn_ra_svn__write_cmd_open_root(eb->conn, pool, rev, token));
194  *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
195  return SVN_NO_ERROR;
196}
197
198static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
199                                        void *parent_baton, apr_pool_t *pool)
200{
201  ra_svn_baton_t *b = parent_baton;
202
203  SVN_ERR(check_for_error(b->eb, pool));
204  SVN_ERR(svn_ra_svn__write_cmd_delete_entry(b->conn, pool,
205                                             path, rev, b->token));
206  return SVN_NO_ERROR;
207}
208
209static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
210                                   const char *copy_path,
211                                   svn_revnum_t copy_rev,
212                                   apr_pool_t *pool, void **child_baton)
213{
214  ra_svn_baton_t *b = parent_baton;
215  svn_string_t *token = make_token('d', b->eb, pool);
216
217  SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
218                 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
219  SVN_ERR(check_for_error(b->eb, pool));
220  SVN_ERR(svn_ra_svn__write_cmd_add_dir(b->conn, pool, path, b->token,
221                                        token, copy_path, copy_rev));
222  *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
223  return SVN_NO_ERROR;
224}
225
226static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
227                                    svn_revnum_t rev, apr_pool_t *pool,
228                                    void **child_baton)
229{
230  ra_svn_baton_t *b = parent_baton;
231  svn_string_t *token = make_token('d', b->eb, pool);
232
233  SVN_ERR(check_for_error(b->eb, pool));
234  SVN_ERR(svn_ra_svn__write_cmd_open_dir(b->conn, pool, path, b->token,
235                                         token, rev));
236  *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
237  return SVN_NO_ERROR;
238}
239
240static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
241                                           const svn_string_t *value,
242                                           apr_pool_t *pool)
243{
244  ra_svn_baton_t *b = dir_baton;
245
246  SVN_ERR(check_for_error(b->eb, pool));
247  SVN_ERR(svn_ra_svn__write_cmd_change_dir_prop(b->conn, pool, b->token,
248                                                name, value));
249  return SVN_NO_ERROR;
250}
251
252static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
253{
254  ra_svn_baton_t *b = dir_baton;
255
256  SVN_ERR(check_for_error(b->eb, pool));
257  SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token));
258  return SVN_NO_ERROR;
259}
260
261static svn_error_t *ra_svn_absent_dir(const char *path,
262                                      void *parent_baton, apr_pool_t *pool)
263{
264  ra_svn_baton_t *b = parent_baton;
265
266  /* Avoid sending an unknown command if the other end doesn't support
267     absent-dir. */
268  if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
269    return SVN_NO_ERROR;
270
271  SVN_ERR(check_for_error(b->eb, pool));
272  SVN_ERR(svn_ra_svn__write_cmd_absent_dir(b->conn, pool, path, b->token));
273  return SVN_NO_ERROR;
274}
275
276static svn_error_t *ra_svn_add_file(const char *path,
277                                    void *parent_baton,
278                                    const char *copy_path,
279                                    svn_revnum_t copy_rev,
280                                    apr_pool_t *pool,
281                                    void **file_baton)
282{
283  ra_svn_baton_t *b = parent_baton;
284  svn_string_t *token = make_token('c', b->eb, pool);
285
286  SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
287                 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
288  SVN_ERR(check_for_error(b->eb, pool));
289  SVN_ERR(svn_ra_svn__write_cmd_add_file(b->conn, pool,  path, b->token,
290                                         token, copy_path, copy_rev));
291  *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
292  return SVN_NO_ERROR;
293}
294
295static svn_error_t *ra_svn_open_file(const char *path,
296                                     void *parent_baton,
297                                     svn_revnum_t rev,
298                                     apr_pool_t *pool,
299                                     void **file_baton)
300{
301  ra_svn_baton_t *b = parent_baton;
302  svn_string_t *token = make_token('c', b->eb, pool);
303
304  SVN_ERR(check_for_error(b->eb, b->pool));
305  SVN_ERR(svn_ra_svn__write_cmd_open_file(b->conn, pool, path, b->token,
306                                          token, rev));
307  *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
308  return SVN_NO_ERROR;
309}
310
311static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
312                                           apr_size_t *len)
313{
314  ra_svn_baton_t *b = baton;
315  svn_string_t str;
316
317  SVN_ERR(check_for_error(b->eb, b->pool));
318  str.data = data;
319  str.len = *len;
320  return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool,
321                                               b->token, &str);
322}
323
324static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
325{
326  ra_svn_baton_t *b = baton;
327
328  SVN_ERR(check_for_error(b->eb, b->pool));
329  SVN_ERR(svn_ra_svn__write_cmd_textdelta_end(b->conn, b->pool, b->token));
330  return SVN_NO_ERROR;
331}
332
333static svn_error_t *ra_svn_apply_textdelta(void *file_baton,
334                                           const char *base_checksum,
335                                           apr_pool_t *pool,
336                                           svn_txdelta_window_handler_t *wh,
337                                           void **wh_baton)
338{
339  ra_svn_baton_t *b = file_baton;
340  svn_stream_t *diff_stream;
341
342  /* Tell the other side we're starting a text delta. */
343  SVN_ERR(check_for_error(b->eb, pool));
344  SVN_ERR(svn_ra_svn__write_cmd_apply_textdelta(b->conn, pool, b->token,
345                                                base_checksum));
346
347  /* Transform the window stream to an svndiff stream.  Reuse the
348   * file baton for the stream handler, since it has all the
349   * needed information. */
350  diff_stream = svn_stream_create(b, pool);
351  svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
352  svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
353
354  svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream,
355                          svn_ra_svn__svndiff_version(b->conn),
356                          b->conn->compression_level, pool);
357  return SVN_NO_ERROR;
358}
359
360static svn_error_t *ra_svn_change_file_prop(void *file_baton,
361                                            const char *name,
362                                            const svn_string_t *value,
363                                            apr_pool_t *pool)
364{
365  ra_svn_baton_t *b = file_baton;
366
367  SVN_ERR(check_for_error(b->eb, pool));
368  SVN_ERR(svn_ra_svn__write_cmd_change_file_prop(b->conn, pool,
369                                                 b->token, name, value));
370  return SVN_NO_ERROR;
371}
372
373static svn_error_t *ra_svn_close_file(void *file_baton,
374                                      const char *text_checksum,
375                                      apr_pool_t *pool)
376{
377  ra_svn_baton_t *b = file_baton;
378
379  SVN_ERR(check_for_error(b->eb, pool));
380  SVN_ERR(svn_ra_svn__write_cmd_close_file(b->conn, pool,
381                                           b->token, text_checksum));
382  return SVN_NO_ERROR;
383}
384
385static svn_error_t *ra_svn_absent_file(const char *path,
386                                       void *parent_baton, apr_pool_t *pool)
387{
388  ra_svn_baton_t *b = parent_baton;
389
390  /* Avoid sending an unknown command if the other end doesn't support
391     absent-file. */
392  if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
393    return SVN_NO_ERROR;
394
395  SVN_ERR(check_for_error(b->eb, pool));
396  SVN_ERR(svn_ra_svn__write_cmd_absent_file(b->conn, pool, path, b->token));
397  return SVN_NO_ERROR;
398}
399
400static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
401{
402  ra_svn_edit_baton_t *eb = edit_baton;
403  svn_error_t *err;
404
405  SVN_ERR_ASSERT(!eb->got_status);
406  eb->got_status = TRUE;
407  SVN_ERR(svn_ra_svn__write_cmd_close_edit(eb->conn, pool));
408  err = svn_error_trace(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
409  if (err)
410    {
411      return svn_error_compose_create(
412                    err,
413                    svn_error_trace(
414                        svn_ra_svn__write_cmd_abort_edit(eb->conn, pool)));
415    }
416  if (eb->callback)
417    SVN_ERR(eb->callback(eb->callback_baton));
418  return SVN_NO_ERROR;
419}
420
421static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
422{
423  ra_svn_edit_baton_t *eb = edit_baton;
424
425  if (eb->got_status)
426    return SVN_NO_ERROR;
427  SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
428  SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
429  return SVN_NO_ERROR;
430}
431
432void svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
433                           void **edit_baton, svn_ra_svn_conn_t *conn,
434                           apr_pool_t *pool,
435                           svn_ra_svn_edit_callback callback,
436                           void *callback_baton)
437{
438  svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool);
439  ra_svn_edit_baton_t *eb;
440
441  eb = apr_palloc(pool, sizeof(*eb));
442  eb->conn = conn;
443  eb->callback = callback;
444  eb->callback_baton = callback_baton;
445  eb->next_token = 0;
446  eb->got_status = FALSE;
447
448  ra_svn_editor->set_target_revision = ra_svn_target_rev;
449  ra_svn_editor->open_root = ra_svn_open_root;
450  ra_svn_editor->delete_entry = ra_svn_delete_entry;
451  ra_svn_editor->add_directory = ra_svn_add_dir;
452  ra_svn_editor->open_directory = ra_svn_open_dir;
453  ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
454  ra_svn_editor->close_directory = ra_svn_close_dir;
455  ra_svn_editor->absent_directory = ra_svn_absent_dir;
456  ra_svn_editor->add_file = ra_svn_add_file;
457  ra_svn_editor->open_file = ra_svn_open_file;
458  ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
459  ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
460  ra_svn_editor->close_file = ra_svn_close_file;
461  ra_svn_editor->absent_file = ra_svn_absent_file;
462  ra_svn_editor->close_edit = ra_svn_close_edit;
463  ra_svn_editor->abort_edit = ra_svn_abort_edit;
464
465  *editor = ra_svn_editor;
466  *edit_baton = eb;
467
468  svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor,
469                                           *edit_baton, NULL, NULL,
470                                           conn->shim_callbacks,
471                                           pool, pool));
472}
473
474/* --- DRIVING AN EDITOR --- */
475
476/* Store a token entry.  The token string will be copied into pool. */
477static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
478                                         void *baton,
479                                         svn_string_t *token,
480                                         svn_boolean_t is_file,
481                                         apr_pool_t *pool)
482{
483  ra_svn_token_entry_t *entry;
484
485  entry = apr_palloc(pool, sizeof(*entry));
486  entry->token = svn_string_dup(token, pool);
487  entry->baton = baton;
488  entry->is_file = is_file;
489  entry->dstream = NULL;
490  entry->pool = pool;
491
492  apr_hash_set(ds->tokens, entry->token->data, entry->token->len, entry);
493  ds->last_token = entry;
494
495  return entry;
496}
497
498static svn_error_t *lookup_token(ra_svn_driver_state_t *ds,
499                                 svn_string_t *token,
500                                 svn_boolean_t is_file,
501                                 ra_svn_token_entry_t **entry)
502{
503  if (ds->last_token && svn_string_compare(ds->last_token->token, token))
504    {
505      *entry = ds->last_token;
506    }
507  else
508    {
509      *entry = apr_hash_get(ds->tokens, token->data, token->len);
510      ds->last_token = *entry;
511    }
512
513  if (!*entry || (*entry)->is_file != is_file)
514    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
515                            _("Invalid file or dir token during edit"));
516  return SVN_NO_ERROR;
517}
518
519/* Remove a TOKEN entry from DS. */
520static void remove_token(ra_svn_driver_state_t *ds,
521                         svn_string_t *token)
522{
523  apr_hash_set(ds->tokens, token->data, token->len, NULL);
524
525  /* Reset this unconditionally.  In most cases, LAST_TOKEN->TOKEN will
526     match TOKEN anyway and if it doesn't, lookup_token() will suffer only
527     a minor performance hit. */
528  ds->last_token = NULL;
529}
530
531static svn_error_t *
532ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
533                         apr_pool_t *pool,
534                         const svn_ra_svn__list_t *params,
535                         ra_svn_driver_state_t *ds)
536{
537  svn_revnum_t rev;
538
539  SVN_ERR(svn_ra_svn__parse_tuple(params, "r", &rev));
540  SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
541  return SVN_NO_ERROR;
542}
543
544static svn_error_t *
545ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
546                        apr_pool_t *pool,
547                        const svn_ra_svn__list_t *params,
548                        ra_svn_driver_state_t *ds)
549{
550  svn_revnum_t rev;
551  apr_pool_t *subpool;
552  svn_string_t *token;
553  void *root_baton;
554
555  SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)s", &rev, &token));
556  subpool = svn_pool_create(ds->pool);
557  SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
558                                    &root_baton));
559  store_token(ds, root_baton, token, FALSE, subpool);
560  return SVN_NO_ERROR;
561}
562
563static svn_error_t *
564ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
565                           apr_pool_t *pool,
566                           const svn_ra_svn__list_t *params,
567                           ra_svn_driver_state_t *ds)
568{
569  const char *path;
570  svn_string_t *token;
571  svn_revnum_t rev;
572  ra_svn_token_entry_t *entry;
573
574  SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)s",
575                                  &path, &rev, &token));
576  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
577  path = svn_relpath_canonicalize(path, pool);
578  SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool));
579  return SVN_NO_ERROR;
580}
581
582static svn_error_t *
583ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
584                      apr_pool_t *pool,
585                      const svn_ra_svn__list_t *params,
586                      ra_svn_driver_state_t *ds)
587{
588  const char *path, *copy_path;
589  svn_string_t *token, *child_token;
590  svn_revnum_t copy_rev;
591  ra_svn_token_entry_t *entry;
592  apr_pool_t *subpool;
593  void *child_baton;
594
595  SVN_ERR(svn_ra_svn__parse_tuple(params, "css(?cr)", &path, &token,
596                                  &child_token, &copy_path, &copy_rev));
597  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
598  subpool = svn_pool_create(entry->pool);
599  path = svn_relpath_canonicalize(path, pool);
600
601  /* Some operations pass COPY_PATH as a full URL (commits, etc.).
602     Others (replay, e.g.) deliver an fspath.  That's ... annoying. */
603  if (copy_path)
604    {
605      if (svn_path_is_url(copy_path))
606        copy_path = svn_uri_canonicalize(copy_path, pool);
607      else
608        copy_path = svn_fspath__canonicalize(copy_path, pool);
609    }
610
611  SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
612                                        copy_rev, subpool, &child_baton));
613  store_token(ds, child_baton, child_token, FALSE, subpool);
614  return SVN_NO_ERROR;
615}
616
617static svn_error_t *
618ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
619                       apr_pool_t *pool,
620                       const svn_ra_svn__list_t *params,
621                       ra_svn_driver_state_t *ds)
622{
623  const char *path;
624  svn_string_t *token, *child_token;
625  svn_revnum_t rev;
626  ra_svn_token_entry_t *entry;
627  apr_pool_t *subpool;
628  void *child_baton;
629
630  SVN_ERR(svn_ra_svn__parse_tuple(params, "css(?r)", &path, &token,
631                                  &child_token, &rev));
632  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
633  subpool = svn_pool_create(entry->pool);
634  path = svn_relpath_canonicalize(path, pool);
635  SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
636                                         &child_baton));
637  store_token(ds, child_baton, child_token, FALSE, subpool);
638  return SVN_NO_ERROR;
639}
640
641static svn_error_t *
642ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
643                              apr_pool_t *pool,
644                              const svn_ra_svn__list_t *params,
645                              ra_svn_driver_state_t *ds)
646{
647  svn_string_t *token;
648  const char *name;
649  svn_string_t *value;
650  ra_svn_token_entry_t *entry;
651
652  SVN_ERR(svn_ra_svn__parse_tuple(params, "sc(?s)", &token, &name,
653                                  &value));
654  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
655  SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
656                                          entry->pool));
657  return SVN_NO_ERROR;
658}
659
660static svn_error_t *
661ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
662                        apr_pool_t *pool,
663                        const svn_ra_svn__list_t *params,
664                        ra_svn_driver_state_t *ds)
665{
666  svn_string_t *token;
667  ra_svn_token_entry_t *entry;
668
669  /* Parse and look up the directory token. */
670  SVN_ERR(svn_ra_svn__parse_tuple(params, "s", &token));
671  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
672
673  /* Close the directory and destroy the baton. */
674  SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
675  remove_token(ds, token);
676  svn_pool_destroy(entry->pool);
677  return SVN_NO_ERROR;
678}
679
680static svn_error_t *
681ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn,
682                         apr_pool_t *pool,
683                         const svn_ra_svn__list_t *params,
684                         ra_svn_driver_state_t *ds)
685{
686  const char *path;
687  svn_string_t *token;
688  ra_svn_token_entry_t *entry;
689
690  /* Parse parameters and look up the directory token. */
691  SVN_ERR(svn_ra_svn__parse_tuple(params, "cs", &path, &token));
692  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
693
694  /* Call the editor. */
695  SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool));
696  return SVN_NO_ERROR;
697}
698
699static svn_error_t *
700ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
701                       apr_pool_t *pool,
702                       const svn_ra_svn__list_t *params,
703                       ra_svn_driver_state_t *ds)
704{
705  const char *path, *copy_path;
706  svn_string_t *token, *file_token;
707  svn_revnum_t copy_rev;
708  ra_svn_token_entry_t *entry, *file_entry;
709
710  SVN_ERR(svn_ra_svn__parse_tuple(params, "css(?cr)", &path, &token,
711                                  &file_token, &copy_path, &copy_rev));
712  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
713  ds->file_refs++;
714
715  /* The PATH should be canonical .. but never trust incoming data. */
716  if (!svn_relpath_is_canonical(path))
717    path = svn_relpath_canonicalize(path, pool);
718
719  /* Some operations pass COPY_PATH as a full URL (commits, etc.).
720     Others (replay, e.g.) deliver an fspath.  That's ... annoying. */
721  if (copy_path)
722    {
723      if (svn_path_is_url(copy_path))
724        copy_path = svn_uri_canonicalize(copy_path, pool);
725      else
726        copy_path = svn_fspath__canonicalize(copy_path, pool);
727    }
728
729  file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
730  SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev,
731                                   ds->file_pool, &file_entry->baton));
732  return SVN_NO_ERROR;
733}
734
735static svn_error_t *
736ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
737                        apr_pool_t *pool,
738                        const svn_ra_svn__list_t *params,
739                        ra_svn_driver_state_t *ds)
740{
741  const char *path;
742  svn_string_t *token, *file_token;
743  svn_revnum_t rev;
744  ra_svn_token_entry_t *entry, *file_entry;
745
746  SVN_ERR(svn_ra_svn__parse_tuple(params, "css(?r)", &path, &token,
747                                  &file_token, &rev));
748  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
749  ds->file_refs++;
750
751  /* The PATH should be canonical .. but never trust incoming data. */
752  if (!svn_relpath_is_canonical(path))
753    path = svn_relpath_canonicalize(path, pool);
754
755  file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
756  SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool,
757                                    &file_entry->baton));
758  return SVN_NO_ERROR;
759}
760
761static svn_error_t *
762ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
763                              apr_pool_t *pool,
764                              const svn_ra_svn__list_t *params,
765                              ra_svn_driver_state_t *ds)
766{
767  svn_string_t *token;
768  ra_svn_token_entry_t *entry;
769  svn_txdelta_window_handler_t wh;
770  void *wh_baton;
771  char *base_checksum;
772
773  /* Parse arguments and look up the token. */
774  SVN_ERR(svn_ra_svn__parse_tuple(params, "s(?c)",
775                                  &token, &base_checksum));
776  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
777  if (entry->dstream)
778    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
779                            _("Apply-textdelta already active"));
780  entry->pool = svn_pool_create(ds->file_pool);
781  SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
782                                          entry->pool, &wh, &wh_baton));
783  entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
784  return SVN_NO_ERROR;
785}
786
787static svn_error_t *
788ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn,
789                              apr_pool_t *pool,
790                              const svn_ra_svn__list_t *params,
791                              ra_svn_driver_state_t *ds)
792{
793  svn_string_t *token;
794  ra_svn_token_entry_t *entry;
795  svn_string_t *str;
796
797  /* Parse arguments and look up the token. */
798  SVN_ERR(svn_ra_svn__parse_tuple(params, "ss", &token, &str));
799  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
800  if (!entry->dstream)
801    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
802                            _("Apply-textdelta not active"));
803  SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
804  return SVN_NO_ERROR;
805}
806
807static svn_error_t *
808ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn,
809                            apr_pool_t *pool,
810                            const svn_ra_svn__list_t *params,
811                            ra_svn_driver_state_t *ds)
812{
813  svn_string_t *token;
814  ra_svn_token_entry_t *entry;
815
816  /* Parse arguments and look up the token. */
817  SVN_ERR(svn_ra_svn__parse_tuple(params, "s", &token));
818  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
819  if (!entry->dstream)
820    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
821                            _("Apply-textdelta not active"));
822  SVN_CMD_ERR(svn_stream_close(entry->dstream));
823  entry->dstream = NULL;
824  svn_pool_destroy(entry->pool);
825  return SVN_NO_ERROR;
826}
827
828static svn_error_t *
829ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
830                               apr_pool_t *pool,
831                               const svn_ra_svn__list_t *params,
832                               ra_svn_driver_state_t *ds)
833{
834  const char *name;
835  svn_string_t *token, *value;
836  ra_svn_token_entry_t *entry;
837
838  SVN_ERR(svn_ra_svn__parse_tuple(params, "sc(?s)", &token, &name,
839                                  &value));
840  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
841  SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
842  return SVN_NO_ERROR;
843}
844
845static svn_error_t *
846ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
847                         apr_pool_t *pool,
848                         const svn_ra_svn__list_t *params,
849                         ra_svn_driver_state_t *ds)
850{
851  svn_string_t *token;
852  ra_svn_token_entry_t *entry;
853  const char *text_checksum;
854
855  /* Parse arguments and look up the file token. */
856  SVN_ERR(svn_ra_svn__parse_tuple(params, "s(?c)",
857                                  &token, &text_checksum));
858  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
859
860  /* Close the file and destroy the baton. */
861  SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
862  remove_token(ds, token);
863  if (--ds->file_refs == 0)
864    svn_pool_clear(ds->file_pool);
865  return SVN_NO_ERROR;
866}
867
868static svn_error_t *
869ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn,
870                          apr_pool_t *pool,
871                          const svn_ra_svn__list_t *params,
872                          ra_svn_driver_state_t *ds)
873{
874  const char *path;
875  svn_string_t *token;
876  ra_svn_token_entry_t *entry;
877
878  /* Parse parameters and look up the parent directory token. */
879  SVN_ERR(svn_ra_svn__parse_tuple(params, "cs", &path, &token));
880  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
881
882  /* Call the editor. */
883  SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool));
884  return SVN_NO_ERROR;
885}
886
887static svn_error_t *
888ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
889                         apr_pool_t *pool,
890                         const svn_ra_svn__list_t *params,
891                         ra_svn_driver_state_t *ds)
892{
893  SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
894  ds->done = TRUE;
895#ifdef SVN_DEBUG
896  /* Before enabling this in non-maintainer mode:
897     *  Note that this code is used on both client *and* server */
898  if (apr_hash_count(ds->tokens) != 0)
899    return svn_error_create(
900              SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION, NULL,
901              _("Closing editor with directories or files open"));
902#endif
903  if (ds->aborted)
904    *ds->aborted = FALSE;
905  return svn_ra_svn__write_cmd_response(conn, pool, "");
906}
907
908static svn_error_t *
909ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
910                         apr_pool_t *pool,
911                         const svn_ra_svn__list_t *params,
912                         ra_svn_driver_state_t *ds)
913{
914  ds->done = TRUE;
915  if (ds->aborted)
916    *ds->aborted = TRUE;
917  SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
918  return svn_ra_svn__write_cmd_response(conn, pool, "");
919}
920
921static svn_error_t *
922ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
923                            apr_pool_t *pool,
924                            const svn_ra_svn__list_t *params,
925                            ra_svn_driver_state_t *ds)
926{
927  if (!ds->for_replay)
928    return svn_error_createf
929      (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
930       _("Command 'finish-replay' invalid outside of replays"));
931  ds->done = TRUE;
932  if (ds->aborted)
933    *ds->aborted = FALSE;
934  return SVN_NO_ERROR;
935}
936
937/* Common function signature for all editor command handlers. */
938typedef svn_error_t *(*cmd_handler_t)(svn_ra_svn_conn_t *conn,
939                                      apr_pool_t *pool,
940                                      const svn_ra_svn__list_t *params,
941                                      ra_svn_driver_state_t *ds);
942
943static const struct {
944  const char *cmd;
945  cmd_handler_t handler;
946} ra_svn_edit_cmds[] = {
947  { "change-file-prop", ra_svn_handle_change_file_prop },
948  { "open-file",        ra_svn_handle_open_file },
949  { "apply-textdelta",  ra_svn_handle_apply_textdelta },
950  { "textdelta-chunk",  ra_svn_handle_textdelta_chunk },
951  { "close-file",       ra_svn_handle_close_file },
952  { "add-dir",          ra_svn_handle_add_dir },
953  { "open-dir",         ra_svn_handle_open_dir },
954  { "change-dir-prop",  ra_svn_handle_change_dir_prop },
955  { "delete-entry",     ra_svn_handle_delete_entry },
956  { "close-dir",        ra_svn_handle_close_dir },
957  { "absent-dir",       ra_svn_handle_absent_dir },
958  { "add-file",         ra_svn_handle_add_file },
959  { "textdelta-end",    ra_svn_handle_textdelta_end },
960  { "absent-file",      ra_svn_handle_absent_file },
961  { "abort-edit",       ra_svn_handle_abort_edit },
962  { "finish-replay",    ra_svn_handle_finish_replay },
963  { "target-rev",       ra_svn_handle_target_rev },
964  { "open-root",        ra_svn_handle_open_root },
965  { "close-edit",       ra_svn_handle_close_edit },
966  { NULL }
967};
968
969/* All editor commands are kept in a collision-free hash table. */
970
971/* Hash table entry.
972   It is similar to ra_svn_edit_cmds but uses our SVN string type. */
973typedef struct cmd_t {
974  svn_string_t cmd;
975  cmd_handler_t handler;
976} cmd_t;
977
978/* The actual hash table.  It will be filled once before first usage.
979
980   If you add more commands, you may have to tweak the table size to
981   eliminate collisions.  Alternatively, you may modify the hash function.
982
983   Be sure to initialize all elements with 0 as the has conflict detection
984   will rely on it (see init_cmd_hash).
985 */
986#define CMD_HASH_SIZE 67
987static cmd_t cmd_hash[CMD_HASH_SIZE] = { { { NULL } } };
988
989/* Init flag that controls CMD_HASH's atomic initialization. */
990static volatile svn_atomic_t cmd_hash_initialized = FALSE;
991
992/* Super-fast hash function that works very well with the structure of our
993   command words.  It produces no conflicts for them.
994
995   Return the index within CMD_HASH that a command NAME of LEN chars would
996   be found.  LEN > 0.
997 */
998static apr_size_t
999cmd_hash_func(const char *name,
1000              apr_size_t len)
1001{
1002  apr_size_t value =     (apr_byte_t)(name[0] - 'a') % 8
1003                   + 1 * (apr_byte_t)(name[len - 1] - 'a') % 8
1004                   + 10 * (len - 7);
1005  return value % CMD_HASH_SIZE;
1006}
1007
1008/* svn_atomic__init_once callback that fills the CMD_HASH table.  It will
1009   error out on hash collisions.  BATON and POOL are not used. */
1010static svn_error_t *
1011init_cmd_hash(void *baton,
1012              apr_pool_t *pool)
1013{
1014  int i;
1015  for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
1016    {
1017      apr_size_t len = strlen(ra_svn_edit_cmds[i].cmd);
1018      apr_size_t value = cmd_hash_func(ra_svn_edit_cmds[i].cmd, len);
1019      SVN_ERR_ASSERT(cmd_hash[value].cmd.data == NULL);
1020
1021      cmd_hash[value].cmd.data = ra_svn_edit_cmds[i].cmd;
1022      cmd_hash[value].cmd.len = len;
1023      cmd_hash[value].handler = ra_svn_edit_cmds[i].handler;
1024    }
1025
1026  return SVN_NO_ERROR;
1027}
1028
1029/* Return the command handler function for the command name CMD.
1030   Return NULL if no such handler exists */
1031static cmd_handler_t
1032cmd_lookup(const char *cmd)
1033{
1034  apr_size_t value;
1035  apr_size_t len = strlen(cmd);
1036
1037  /* Malicious data that our hash function may not like? */
1038  if (len == 0)
1039    return NULL;
1040
1041  /* Hash lookup. */
1042  value = cmd_hash_func(cmd, len);
1043
1044  /* Hit? */
1045  if (cmd_hash[value].cmd.len != len)
1046    return NULL;
1047
1048  if (memcmp(cmd_hash[value].cmd.data, cmd, len))
1049    return NULL;
1050
1051  /* Yes! */
1052  return cmd_hash[value].handler;
1053}
1054
1055static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1056                                  void *baton)
1057{
1058  ra_svn_driver_state_t *ds = baton;
1059  const char *cmd;
1060  svn_ra_svn__list_t *params;
1061
1062  /* We blocked trying to send an error.  Read and discard an editing
1063   * command in order to avoid deadlock. */
1064  SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &cmd, &params));
1065  if (strcmp(cmd, "abort-edit") == 0)
1066    {
1067      ds->done = TRUE;
1068      svn_ra_svn__set_block_handler(conn, NULL, NULL);
1069    }
1070  return SVN_NO_ERROR;
1071}
1072
1073svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
1074                                      apr_pool_t *pool,
1075                                      const svn_delta_editor_t *editor,
1076                                      void *edit_baton,
1077                                      svn_boolean_t *aborted,
1078                                      svn_boolean_t for_replay)
1079{
1080  ra_svn_driver_state_t state;
1081  apr_pool_t *subpool = svn_pool_create(pool);
1082  const char *cmd;
1083  svn_error_t *err, *write_err;
1084  svn_ra_svn__list_t *params;
1085
1086  SVN_ERR(svn_atomic__init_once(&cmd_hash_initialized, init_cmd_hash, NULL,
1087                                pool));
1088
1089  state.editor = editor;
1090  state.edit_baton = edit_baton;
1091  state.tokens = svn_hash__make(pool);
1092  state.last_token = NULL;
1093  state.aborted = aborted;
1094  state.done = FALSE;
1095  state.pool = pool;
1096  state.file_pool = svn_pool_create(pool);
1097  state.file_refs = 0;
1098  state.for_replay = for_replay;
1099
1100  while (!state.done)
1101    {
1102      svn_pool_clear(subpool);
1103
1104      /* WRT to applying I/O limits, treat each editor command as a separate
1105       * protocol command. */
1106      svn_ra_svn__reset_command_io_counters(conn);
1107      if (editor)
1108        {
1109          cmd_handler_t handler;
1110          SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, &params));
1111          handler = cmd_lookup(cmd);
1112
1113          if (handler)
1114            err = (*handler)(conn, subpool, params, &state);
1115          else if (strcmp(cmd, "failure") == 0)
1116            {
1117              /* While not really an editor command this can occur when
1118                reporter->finish_report() fails before the first editor
1119                command */
1120              if (aborted)
1121                *aborted = TRUE;
1122              err = svn_ra_svn__handle_failure_status(params);
1123              return svn_error_compose_create(
1124                                err,
1125                                editor->abort_edit(edit_baton, subpool));
1126            }
1127          else
1128            {
1129              err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
1130                                      _("Unknown editor command '%s'"), cmd);
1131              err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
1132            }
1133        }
1134      else
1135        {
1136          const char* command = NULL;
1137          SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command));
1138          if (strcmp(command, "close-edit") == 0)
1139            {
1140              state.done = TRUE;
1141              if (aborted)
1142                *aborted = FALSE;
1143              err = svn_ra_svn__write_cmd_response(conn, pool, "");
1144            }
1145          else
1146            err = NULL;
1147        }
1148
1149      if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
1150        {
1151          if (aborted)
1152            *aborted = TRUE;
1153          if (!state.done)
1154            {
1155              /* Abort the edit and use non-blocking I/O to write the error. */
1156              if (editor)
1157                {
1158                  err = svn_error_compose_create(
1159                          err,
1160                          svn_error_trace(editor->abort_edit(edit_baton,
1161                                                             subpool)));
1162                }
1163              svn_ra_svn__set_block_handler(conn, blocked_write, &state);
1164            }
1165          write_err = svn_ra_svn__write_cmd_failure(
1166                          conn, subpool,
1167                          svn_ra_svn__locate_real_error_child(err));
1168          if (!write_err)
1169            write_err = svn_ra_svn__flush(conn, subpool);
1170          svn_ra_svn__set_block_handler(conn, NULL, NULL);
1171          svn_error_clear(err); /* We just sent this error */
1172          SVN_ERR(write_err);
1173          break;
1174        }
1175      SVN_ERR(err);
1176    }
1177
1178  /* Read and discard editing commands until the edit is complete.
1179     Hopefully, the other side will call another editor command, run
1180     check_for_error, notice the error, write "abort-edit" at us, and
1181     throw the error up a few levels on its side (possibly even
1182     tossing it right back at us, which is why we can return
1183     SVN_NO_ERROR below).
1184
1185     However, if the other side is way ahead of us, it might
1186     completely finish the edit (or sequence of edit/revprops, for
1187     "replay-range") before we send over our "failure".  So we should
1188     also stop if we see "success".  (Then the other side will try to
1189     interpret our "failure" as a command, which will itself fail...
1190     The net effect is that whatever error we wrote to the other side
1191     will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.)
1192   */
1193  while (!state.done)
1194    {
1195      svn_pool_clear(subpool);
1196      err = svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, &params);
1197      if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED)
1198        {
1199          /* Other side disconnected; that's no error. */
1200          svn_error_clear(err);
1201          svn_pool_destroy(subpool);
1202          return SVN_NO_ERROR;
1203        }
1204      svn_error_clear(err);
1205      if (strcmp(cmd, "abort-edit") == 0
1206          || strcmp(cmd, "success") == 0)
1207        state.done = TRUE;
1208    }
1209
1210  svn_pool_destroy(subpool);
1211  return SVN_NO_ERROR;
1212}
1213
1214svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1215                                     const svn_delta_editor_t *editor,
1216                                     void *edit_baton,
1217                                     svn_boolean_t *aborted)
1218{
1219  return svn_ra_svn_drive_editor2(conn,
1220                                  pool,
1221                                  editor,
1222                                  edit_baton,
1223                                  aborted,
1224                                  FALSE);
1225}
1226