1 /*
2 * wc_editor.c: editing the local modifications in the WC.
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 /*** Includes. ***/
27
28 #include <string.h>
29 #include "svn_hash.h"
30 #include "svn_client.h"
31 #include "svn_delta.h"
32 #include "svn_dirent_uri.h"
33 #include "svn_error.h"
34 #include "svn_error_codes.h"
35 #include "svn_pools.h"
36 #include "svn_props.h"
37 #include "svn_wc.h"
38
39 #include <apr_md5.h>
40
41 #include "client.h"
42 #include "private/svn_subr_private.h"
43 #include "private/svn_wc_private.h"
44 #include "svn_private_config.h"
45
46
47 /* ------------------------------------------------------------------ */
48
49 /* WC Modifications Editor.
50 *
51 * This editor applies incoming modifications onto the current working state
52 * of the working copy, to produce a new working state.
53 *
54 * Currently, it assumes the working state matches what the edit driver
55 * expects to find, and may throw an error if not.
56 *
57 * For simplicity, we apply incoming edits as they arrive, rather than
58 * queueing them up to apply in a batch.
59 *
60 * TODO:
61 * - tests
62 * - use for all existing scenarios ('svn add', 'svn propset', etc.)
63 * - Instead of 'root_dir_add' option, probably the driver should anchor
64 * at the parent dir.
65 * - Instead of 'ignore_mergeinfo' option, implement that as a wrapper.
66 * - Option to quietly accept changes that seem to be already applied
67 * in the versioned state and/or on disk.
68 * Consider 'svn add' which assumes items to be added are found on disk.
69 * - Notification.
70 */
71
72 /* Everything we need to know about the edit session.
73 */
74 struct edit_baton_t
75 {
76 const char *anchor_abspath;
77 svn_boolean_t manage_wc_write_lock;
78 const char *lock_root_abspath; /* the path locked, when locked */
79
80 /* True => 'open_root' method will act as 'add_directory' */
81 svn_boolean_t root_dir_add;
82 /* True => filter out any incoming svn:mergeinfo property changes */
83 svn_boolean_t ignore_mergeinfo_changes;
84
85 svn_ra_session_t *ra_session;
86
87 svn_wc_context_t *wc_ctx;
88 svn_client_ctx_t *ctx;
89 svn_wc_notify_func2_t notify_func;
90 void *notify_baton;
91 };
92
93 /* Everything we need to know about a directory that's open for edits.
94 */
95 struct dir_baton_t
96 {
97 apr_pool_t *pool;
98
99 struct edit_baton_t *eb;
100
101 const char *local_abspath;
102 };
103
104 /* Join PATH onto ANCHOR_ABSPATH.
105 * Throw an error if the result is outside ANCHOR_ABSPATH.
106 */
107 static svn_error_t *
get_path(const char ** local_abspath_p,const char * anchor_abspath,const char * path,apr_pool_t * result_pool)108 get_path(const char **local_abspath_p,
109 const char *anchor_abspath,
110 const char *path,
111 apr_pool_t *result_pool)
112 {
113 svn_boolean_t under_root;
114
115 SVN_ERR(svn_dirent_is_under_root(&under_root, local_abspath_p,
116 anchor_abspath, path, result_pool));
117 if (! under_root)
118 {
119 return svn_error_createf(
120 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
121 _("Path '%s' is not in the working copy"),
122 svn_dirent_local_style(path, result_pool));
123 }
124 return SVN_NO_ERROR;
125 }
126
127 /* Create a directory on disk and add it to version control,
128 * with no properties.
129 */
130 static svn_error_t *
mkdir(const char * abspath,struct edit_baton_t * eb,apr_pool_t * scratch_pool)131 mkdir(const char *abspath,
132 struct edit_baton_t *eb,
133 apr_pool_t *scratch_pool)
134 {
135 SVN_ERR(svn_io_make_dir_recursively(abspath, scratch_pool));
136 SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath,
137 NULL /*properties*/,
138 TRUE /* skip checks */,
139 eb->notify_func, eb->notify_baton,
140 scratch_pool));
141 return SVN_NO_ERROR;
142 }
143
144 /* Prepare to open or add a directory: initialize a new dir baton.
145 *
146 * If PATH is "" and PB is null, it represents the root directory of
147 * the edit; otherwise PATH is not "" and PB is not null.
148 */
149 static svn_error_t *
dir_open_or_add(struct dir_baton_t ** child_dir_baton,const char * path,struct dir_baton_t * pb,struct edit_baton_t * eb,apr_pool_t * dir_pool)150 dir_open_or_add(struct dir_baton_t **child_dir_baton,
151 const char *path,
152 struct dir_baton_t *pb,
153 struct edit_baton_t *eb,
154 apr_pool_t *dir_pool)
155 {
156 struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
157
158 db->pool = dir_pool;
159 db->eb = eb;
160
161 SVN_ERR(get_path(&db->local_abspath,
162 eb->anchor_abspath, path, dir_pool));
163
164 *child_dir_baton = db;
165 return SVN_NO_ERROR;
166 }
167
168 /* */
169 static svn_error_t *
release_write_lock(struct edit_baton_t * eb,apr_pool_t * scratch_pool)170 release_write_lock(struct edit_baton_t *eb,
171 apr_pool_t *scratch_pool)
172 {
173 if (eb->lock_root_abspath)
174 {
175 SVN_ERR(svn_wc__release_write_lock(
176 eb->ctx->wc_ctx, eb->lock_root_abspath, scratch_pool));
177 eb->lock_root_abspath = NULL;
178 }
179 return SVN_NO_ERROR;
180 }
181
182 /* */
183 static apr_status_t
pool_cleanup_handler(void * root_baton)184 pool_cleanup_handler(void *root_baton)
185 {
186 struct dir_baton_t *db = root_baton;
187 struct edit_baton_t *eb = db->eb;
188
189 svn_error_clear(release_write_lock(eb, db->pool));
190 return APR_SUCCESS;
191 }
192
193 /* svn_delta_editor_t function */
194 static svn_error_t *
edit_open(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * result_pool,void ** root_baton)195 edit_open(void *edit_baton,
196 svn_revnum_t base_revision,
197 apr_pool_t *result_pool,
198 void **root_baton)
199 {
200 struct edit_baton_t *eb = edit_baton;
201 struct dir_baton_t *db;
202
203 SVN_ERR(dir_open_or_add(&db, "", NULL, eb, result_pool));
204
205 /* Acquire a WC write lock */
206 if (eb->manage_wc_write_lock)
207 {
208 apr_pool_cleanup_register(db->pool, db,
209 pool_cleanup_handler,
210 apr_pool_cleanup_null);
211 SVN_ERR(svn_wc__acquire_write_lock(&eb->lock_root_abspath,
212 eb->ctx->wc_ctx,
213 eb->anchor_abspath,
214 FALSE /*lock_anchor*/,
215 db->pool, db->pool));
216 }
217
218 if (eb->root_dir_add)
219 {
220 SVN_ERR(mkdir(db->local_abspath, eb, result_pool));
221 }
222
223 *root_baton = db;
224 return SVN_NO_ERROR;
225 }
226
227 /* svn_delta_editor_t function */
228 static svn_error_t *
edit_close_or_abort(void * edit_baton,apr_pool_t * scratch_pool)229 edit_close_or_abort(void *edit_baton,
230 apr_pool_t *scratch_pool)
231 {
232 SVN_ERR(release_write_lock(edit_baton, scratch_pool));
233 return SVN_NO_ERROR;
234 }
235
236 /* */
237 static svn_error_t *
delete_entry(const char * path,svn_revnum_t revision,void * parent_baton,apr_pool_t * scratch_pool)238 delete_entry(const char *path,
239 svn_revnum_t revision,
240 void *parent_baton,
241 apr_pool_t *scratch_pool)
242 {
243 struct dir_baton_t *pb = parent_baton;
244 struct edit_baton_t *eb = pb->eb;
245 const char *local_abspath;
246
247 SVN_ERR(get_path(&local_abspath,
248 eb->anchor_abspath, path, scratch_pool));
249 SVN_ERR(svn_wc_delete4(eb->wc_ctx, local_abspath,
250 FALSE /*keep_local*/,
251 TRUE /*delete_unversioned*/,
252 NULL, NULL, /*cancellation*/
253 eb->notify_func, eb->notify_baton, scratch_pool));
254
255 return SVN_NO_ERROR;
256 }
257
258 /* An svn_delta_editor_t function. */
259 static svn_error_t *
dir_open(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * result_pool,void ** child_baton)260 dir_open(const char *path,
261 void *parent_baton,
262 svn_revnum_t base_revision,
263 apr_pool_t *result_pool,
264 void **child_baton)
265 {
266 struct dir_baton_t *pb = parent_baton;
267 struct edit_baton_t *eb = pb->eb;
268 struct dir_baton_t *db;
269
270 SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool));
271
272 *child_baton = db;
273 return SVN_NO_ERROR;
274 }
275
276 static svn_error_t *
dir_add(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * result_pool,void ** child_baton)277 dir_add(const char *path,
278 void *parent_baton,
279 const char *copyfrom_path,
280 svn_revnum_t copyfrom_revision,
281 apr_pool_t *result_pool,
282 void **child_baton)
283 {
284 struct dir_baton_t *pb = parent_baton;
285 struct edit_baton_t *eb = pb->eb;
286 struct dir_baton_t *db;
287 /* ### Our caller should be providing a scratch pool */
288 apr_pool_t *scratch_pool = svn_pool_create(result_pool);
289
290 SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool));
291
292 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision))
293 {
294 SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/,
295 svn_node_dir,
296 copyfrom_path,
297 copyfrom_revision,
298 db->local_abspath,
299 db->eb->ra_session,
300 db->eb->ctx,
301 scratch_pool));
302 }
303 else
304 {
305 SVN_ERR(mkdir(db->local_abspath, eb, result_pool));
306 }
307
308 *child_baton = db;
309 svn_pool_destroy(scratch_pool);
310 return SVN_NO_ERROR;
311 }
312
313 static svn_error_t *
dir_change_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)314 dir_change_prop(void *dir_baton,
315 const char *name,
316 const svn_string_t *value,
317 apr_pool_t *scratch_pool)
318 {
319 struct dir_baton_t *db = dir_baton;
320 struct edit_baton_t *eb = db->eb;
321
322 if (svn_property_kind2(name) != svn_prop_regular_kind
323 || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO)))
324 {
325 /* We can't handle DAV, ENTRY and merge specific props here */
326 return SVN_NO_ERROR;
327 }
328
329 SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value,
330 svn_depth_empty, FALSE, NULL,
331 NULL, NULL, /* Cancellation */
332 NULL, NULL, /* Notification */
333 scratch_pool));
334
335 return SVN_NO_ERROR;
336 }
337
338 static svn_error_t *
dir_close(void * dir_baton,apr_pool_t * scratch_pool)339 dir_close(void *dir_baton,
340 apr_pool_t *scratch_pool)
341 {
342 return SVN_NO_ERROR;
343 }
344
345 /* Everything we need to know about a file that's open for edits.
346 */
347 struct file_baton_t
348 {
349 apr_pool_t *pool;
350
351 struct edit_baton_t *eb;
352
353 const char *local_abspath;
354
355 /* fields for the transfer of text changes */
356 const char *writing_file;
357 unsigned char digest[APR_MD5_DIGESTSIZE]; /* MD5 digest of new fulltext */
358 svn_stream_t *wc_file_read_stream, *tmp_file_write_stream;
359 const char *tmp_path;
360 };
361
362 /* Create a new file on disk and add it to version control.
363 *
364 * The file is empty and has no properties.
365 */
366 static svn_error_t *
mkfile(const char * abspath,struct edit_baton_t * eb,apr_pool_t * scratch_pool)367 mkfile(const char *abspath,
368 struct edit_baton_t *eb,
369 apr_pool_t *scratch_pool)
370 {
371 SVN_ERR(svn_io_file_create_empty(abspath, scratch_pool));
372 SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath,
373 NULL /*properties*/,
374 TRUE /* skip checks */,
375 eb->notify_func, eb->notify_baton,
376 scratch_pool));
377 return SVN_NO_ERROR;
378 }
379
380 /* */
381 static svn_error_t *
file_open_or_add(const char * path,void * parent_baton,struct file_baton_t ** file_baton,apr_pool_t * file_pool)382 file_open_or_add(const char *path,
383 void *parent_baton,
384 struct file_baton_t **file_baton,
385 apr_pool_t *file_pool)
386 {
387 struct dir_baton_t *pb = parent_baton;
388 struct edit_baton_t *eb = pb->eb;
389 struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
390
391 fb->pool = file_pool;
392 fb->eb = eb;
393 SVN_ERR(get_path(&fb->local_abspath,
394 eb->anchor_abspath, path, fb->pool));
395
396 *file_baton = fb;
397 return SVN_NO_ERROR;
398 }
399
400 static svn_error_t *
file_open(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * result_pool,void ** file_baton)401 file_open(const char *path,
402 void *parent_baton,
403 svn_revnum_t base_revision,
404 apr_pool_t *result_pool,
405 void **file_baton)
406 {
407 struct file_baton_t *fb;
408
409 SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool));
410
411 *file_baton = fb;
412 return SVN_NO_ERROR;
413 }
414
415 static svn_error_t *
file_add(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * result_pool,void ** file_baton)416 file_add(const char *path,
417 void *parent_baton,
418 const char *copyfrom_path,
419 svn_revnum_t copyfrom_revision,
420 apr_pool_t *result_pool,
421 void **file_baton)
422 {
423 struct file_baton_t *fb;
424
425 SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool));
426
427 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision))
428 {
429 SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/,
430 svn_node_file,
431 copyfrom_path,
432 copyfrom_revision,
433 fb->local_abspath,
434 fb->eb->ra_session,
435 fb->eb->ctx, fb->pool));
436 }
437 else
438 {
439 SVN_ERR(mkfile(fb->local_abspath, fb->eb, result_pool));
440 }
441
442 *file_baton = fb;
443 return SVN_NO_ERROR;
444 }
445
446 static svn_error_t *
file_change_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)447 file_change_prop(void *file_baton,
448 const char *name,
449 const svn_string_t *value,
450 apr_pool_t *scratch_pool)
451 {
452 struct file_baton_t *fb = file_baton;
453 struct edit_baton_t *eb = fb->eb;
454
455 if (svn_property_kind2(name) != svn_prop_regular_kind
456 || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO)))
457 {
458 /* We can't handle DAV, ENTRY and merge specific props here */
459 return SVN_NO_ERROR;
460 }
461
462 SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, fb->local_abspath, name, value,
463 svn_depth_empty, FALSE, NULL,
464 NULL, NULL, /* Cancellation */
465 NULL, NULL, /* Notification */
466 scratch_pool));
467
468 return SVN_NO_ERROR;
469 }
470
471 static svn_error_t *
file_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * result_pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)472 file_textdelta(void *file_baton,
473 const char *base_checksum,
474 apr_pool_t *result_pool,
475 svn_txdelta_window_handler_t *handler,
476 void **handler_baton)
477 {
478 struct file_baton_t *fb = file_baton;
479 const char *target_dir = svn_dirent_dirname(fb->local_abspath, fb->pool);
480 svn_error_t *err;
481
482 SVN_ERR_ASSERT(! fb->writing_file);
483
484 err = svn_stream_open_readonly(&fb->wc_file_read_stream, fb->local_abspath,
485 fb->pool, fb->pool);
486 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
487 {
488 svn_error_clear(err);
489 fb->wc_file_read_stream = svn_stream_empty(fb->pool);
490 }
491 else
492 SVN_ERR(err);
493
494 SVN_ERR(svn_stream_open_unique(&fb->tmp_file_write_stream, &fb->writing_file,
495 target_dir, svn_io_file_del_none,
496 fb->pool, fb->pool));
497
498 svn_txdelta_apply(fb->wc_file_read_stream,
499 fb->tmp_file_write_stream,
500 fb->digest,
501 fb->local_abspath,
502 fb->pool,
503 /* Provide the handler directly */
504 handler, handler_baton);
505
506 return SVN_NO_ERROR;
507 }
508
509 static svn_error_t *
file_close(void * file_baton,const char * text_checksum,apr_pool_t * scratch_pool)510 file_close(void *file_baton,
511 const char *text_checksum,
512 apr_pool_t *scratch_pool)
513 {
514 struct file_baton_t *fb = file_baton;
515
516 /* If we have text changes, write them to disk */
517 if (fb->writing_file)
518 {
519 SVN_ERR(svn_stream_close(fb->wc_file_read_stream));
520 SVN_ERR(svn_io_file_rename2(fb->writing_file, fb->local_abspath,
521 FALSE /*flush*/, scratch_pool));
522 }
523
524 if (text_checksum)
525 {
526 svn_checksum_t *expected_checksum;
527 svn_checksum_t *actual_checksum;
528
529 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
530 text_checksum, fb->pool));
531 actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool);
532
533 if (! svn_checksum_match(expected_checksum, actual_checksum))
534 return svn_error_trace(
535 svn_checksum_mismatch_err(expected_checksum,
536 actual_checksum,
537 fb->pool,
538 _("Checksum mismatch for '%s'"),
539 svn_dirent_local_style(
540 fb->local_abspath,
541 fb->pool)));
542 }
543
544 return SVN_NO_ERROR;
545 }
546
547 svn_error_t *
svn_client__wc_editor_internal(const svn_delta_editor_t ** editor_p,void ** edit_baton_p,const char * dst_abspath,svn_boolean_t root_dir_add,svn_boolean_t ignore_mergeinfo_changes,svn_boolean_t manage_wc_write_lock,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * result_pool)548 svn_client__wc_editor_internal(const svn_delta_editor_t **editor_p,
549 void **edit_baton_p,
550 const char *dst_abspath,
551 svn_boolean_t root_dir_add,
552 svn_boolean_t ignore_mergeinfo_changes,
553 svn_boolean_t manage_wc_write_lock,
554 svn_wc_notify_func2_t notify_func,
555 void *notify_baton,
556 svn_ra_session_t *ra_session,
557 svn_client_ctx_t *ctx,
558 apr_pool_t *result_pool)
559 {
560 svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
561 struct edit_baton_t *eb = apr_pcalloc(result_pool, sizeof(*eb));
562
563 eb->anchor_abspath = apr_pstrdup(result_pool, dst_abspath);
564 eb->manage_wc_write_lock = manage_wc_write_lock;
565 eb->lock_root_abspath = NULL;
566 eb->root_dir_add = root_dir_add;
567 eb->ignore_mergeinfo_changes = ignore_mergeinfo_changes;
568
569 eb->ra_session = ra_session;
570 eb->wc_ctx = ctx->wc_ctx;
571 eb->ctx = ctx;
572 eb->notify_func = notify_func;
573 eb->notify_baton = notify_baton;
574
575 editor->open_root = edit_open;
576 editor->close_edit = edit_close_or_abort;
577 editor->abort_edit = edit_close_or_abort;
578
579 editor->delete_entry = delete_entry;
580
581 editor->open_directory = dir_open;
582 editor->add_directory = dir_add;
583 editor->change_dir_prop = dir_change_prop;
584 editor->close_directory = dir_close;
585
586 editor->open_file = file_open;
587 editor->add_file = file_add;
588 editor->change_file_prop = file_change_prop;
589 editor->apply_textdelta = file_textdelta;
590 editor->close_file = file_close;
591
592 *editor_p = editor;
593 *edit_baton_p = eb;
594 return SVN_NO_ERROR;
595 }
596
597 svn_error_t *
svn_client__wc_editor(const svn_delta_editor_t ** editor_p,void ** edit_baton_p,const char * dst_abspath,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * result_pool)598 svn_client__wc_editor(const svn_delta_editor_t **editor_p,
599 void **edit_baton_p,
600 const char *dst_abspath,
601 svn_wc_notify_func2_t notify_func,
602 void *notify_baton,
603 svn_ra_session_t *ra_session,
604 svn_client_ctx_t *ctx,
605 apr_pool_t *result_pool)
606 {
607 SVN_ERR(svn_client__wc_editor_internal(editor_p, edit_baton_p,
608 dst_abspath,
609 FALSE /*root_dir_add*/,
610 FALSE /*ignore_mergeinfo_changes*/,
611 TRUE /*manage_wc_write_lock*/,
612 notify_func, notify_baton,
613 ra_session,
614 ctx, result_pool));
615 return SVN_NO_ERROR;
616 }
617
618 svn_error_t *
svn_client__wc_copy_mods(const char * src_wc_abspath,const char * dst_wc_abspath,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)619 svn_client__wc_copy_mods(const char *src_wc_abspath,
620 const char *dst_wc_abspath,
621 svn_wc_notify_func2_t notify_func,
622 void *notify_baton,
623 svn_client_ctx_t *ctx,
624 apr_pool_t *scratch_pool)
625 {
626 svn_client__pathrev_t *base;
627 const char *dst_wc_url;
628 svn_ra_session_t *ra_session;
629 const svn_delta_editor_t *editor;
630 void *edit_baton;
631 apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1,
632 sizeof(char *));
633
634 /* We'll need an RA session to obtain the base of any copies */
635 SVN_ERR(svn_client__wc_node_get_base(&base,
636 src_wc_abspath, ctx->wc_ctx,
637 scratch_pool, scratch_pool));
638 dst_wc_url = base->url;
639 SVN_ERR(svn_client_open_ra_session2(&ra_session,
640 dst_wc_url, dst_wc_abspath,
641 ctx, scratch_pool, scratch_pool));
642 SVN_ERR(svn_client__wc_editor(&editor, &edit_baton,
643 dst_wc_abspath,
644 NULL, NULL, /*notification*/
645 ra_session, ctx, scratch_pool));
646
647 APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath;
648 SVN_ERR(svn_client__wc_replay(src_wc_abspath,
649 src_targets, svn_depth_infinity, NULL,
650 editor, edit_baton,
651 notify_func, notify_baton,
652 ctx, scratch_pool));
653
654 return SVN_NO_ERROR;
655 }
656