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