copy.c revision 289166
1299425Smm/*
2299425Smm * copy.c:  wc 'copy' functionality.
3299425Smm *
4299425Smm * ====================================================================
5299425Smm *    Licensed to the Apache Software Foundation (ASF) under one
6299425Smm *    or more contributor license agreements.  See the NOTICE file
7299425Smm *    distributed with this work for additional information
8299425Smm *    regarding copyright ownership.  The ASF licenses this file
9299425Smm *    to you under the Apache License, Version 2.0 (the
10299425Smm *    "License"); you may not use this file except in compliance
11299425Smm *    with the License.  You may obtain a copy of the License at
12299425Smm *
13299425Smm *      http://www.apache.org/licenses/LICENSE-2.0
14299425Smm *
15299425Smm *    Unless required by applicable law or agreed to in writing,
16299425Smm *    software distributed under the License is distributed on an
17299425Smm *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18299425Smm *    KIND, either express or implied.  See the License for the
19299425Smm *    specific language governing permissions and limitations
20299425Smm *    under the License.
21299425Smm * ====================================================================
22299425Smm */
23299425Smm
24299425Smm/* ==================================================================== */
25299425Smm
26299425Smm
27299425Smm
28299425Smm/*** Includes. ***/
29299425Smm
30299425Smm#include <string.h>
31299425Smm#include "svn_pools.h"
32299425Smm#include "svn_error.h"
33299425Smm#include "svn_dirent_uri.h"
34299425Smm#include "svn_path.h"
35299425Smm#include "svn_hash.h"
36299425Smm
37299425Smm#include "wc.h"
38299425Smm#include "workqueue.h"
39299425Smm#include "props.h"
40299425Smm#include "conflicts.h"
41299425Smm
42299425Smm#include "svn_private_config.h"
43299425Smm#include "private/svn_wc_private.h"
44299425Smm
45299425Smm
46299425Smm/*** Code. ***/
47299425Smm
48299425Smm/* Make a copy of the filesystem node (or tree if RECURSIVE) at
49299425Smm   SRC_ABSPATH under a temporary name in the directory
50299425Smm   TMPDIR_ABSPATH and return the absolute path of the copy in
51299425Smm   *DST_ABSPATH.  Return the node kind of SRC_ABSPATH in *KIND.  If
52299425Smm   SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate
53299425Smm   that no copy was made. */
54299425Smmstatic svn_error_t *
55299425Smmcopy_to_tmpdir(svn_skel_t **work_item,
56299425Smm               svn_node_kind_t *kind,
57299425Smm               svn_wc__db_t *db,
58299425Smm               const char *src_abspath,
59299425Smm               const char *dst_abspath,
60299425Smm               const char *tmpdir_abspath,
61299425Smm               svn_boolean_t file_copy,
62299425Smm               svn_boolean_t unversioned,
63299425Smm               svn_cancel_func_t cancel_func,
64299425Smm               void *cancel_baton,
65299425Smm               apr_pool_t *result_pool,
66299425Smm               apr_pool_t *scratch_pool)
67299425Smm{
68299425Smm  svn_boolean_t is_special;
69299425Smm  svn_io_file_del_t delete_when;
70299425Smm  const char *dst_tmp_abspath;
71299425Smm  svn_node_kind_t dsk_kind;
72299425Smm  if (!kind)
73299425Smm    kind = &dsk_kind;
74299425Smm
75299425Smm  *work_item = NULL;
76299425Smm
77299425Smm  SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special,
78299425Smm                                    scratch_pool));
79299425Smm  if (*kind == svn_node_none)
80299425Smm    {
81299425Smm      return SVN_NO_ERROR;
82299425Smm    }
83299425Smm  else if (*kind == svn_node_unknown)
84299425Smm    {
85299425Smm      return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
86299425Smm                               _("Source '%s' is unexpected kind"),
87299425Smm                               svn_dirent_local_style(src_abspath,
88299425Smm                                                      scratch_pool));
89299425Smm    }
90299425Smm  else if (*kind == svn_node_dir || is_special)
91299425Smm    delete_when = svn_io_file_del_on_close;
92299425Smm  else /* the default case: (*kind == svn_node_file) */
93299425Smm    delete_when = svn_io_file_del_none;
94299425Smm
95299425Smm  /* ### Do we need a pool cleanup to remove the copy?  We can't use
96299425Smm     ### svn_io_file_del_on_pool_cleanup above because a) it won't
97299425Smm     ### handle the directory case and b) we need to be able to remove
98299425Smm     ### the cleanup before queueing the move work item. */
99299425Smm
100299425Smm  if (file_copy && !unversioned)
101299425Smm    {
102299425Smm      svn_boolean_t modified;
103299425Smm      /* It's faster to look for mods on the source now, as
104299425Smm         the timestamp might match, than to examine the
105299425Smm         destination later as the destination timestamp will
106299425Smm         never match. */
107299425Smm      SVN_ERR(svn_wc__internal_file_modified_p(&modified,
108299425Smm                                               db, src_abspath,
109299425Smm                                               FALSE, scratch_pool));
110299425Smm      if (!modified)
111299425Smm        {
112299425Smm          /* Why create a temp copy if we can just reinstall from pristine? */
113299425Smm          SVN_ERR(svn_wc__wq_build_file_install(work_item,
114299425Smm                                                db, dst_abspath, NULL, FALSE,
115299425Smm                                                TRUE,
116299425Smm                                                result_pool, scratch_pool));
117299425Smm          return SVN_NO_ERROR;
118299425Smm        }
119299425Smm    }
120299425Smm
121299425Smm  /* Set DST_TMP_ABSPATH to a temporary unique path.  If *KIND is file, leave
122299425Smm     a file there and then overwrite it; otherwise leave no node on disk at
123299425Smm     that path.  In the latter case, something else might use that path
124299425Smm     before we get around to using it a moment later, but never mind. */
125299425Smm  SVN_ERR(svn_io_open_unique_file3(NULL, &dst_tmp_abspath, tmpdir_abspath,
126299425Smm                                   delete_when, scratch_pool, scratch_pool));
127299425Smm
128299425Smm  if (*kind == svn_node_dir)
129299425Smm    {
130299425Smm      if (file_copy)
131299425Smm        SVN_ERR(svn_io_copy_dir_recursively(
132299425Smm                           src_abspath,
133299425Smm                           tmpdir_abspath,
134299425Smm                           svn_dirent_basename(dst_tmp_abspath, scratch_pool),
135299425Smm                           TRUE, /* copy_perms */
136299425Smm                           cancel_func, cancel_baton,
137299425Smm                           scratch_pool));
138299425Smm      else
139299425Smm        SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool));
140299425Smm    }
141299425Smm  else if (!is_special)
142299425Smm    SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath,
143299425Smm                             TRUE /* copy_perms */,
144299425Smm                             scratch_pool));
145299425Smm  else
146299425Smm    SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool));
147299425Smm
148299425Smm  if (file_copy)
149299425Smm    {
150299425Smm      /* Remove 'read-only' from the destination file; it's a local add now. */
151299425Smm      SVN_ERR(svn_io_set_file_read_write(dst_tmp_abspath,
152299425Smm                                         FALSE, scratch_pool));
153299425Smm    }
154299425Smm
155299425Smm  SVN_ERR(svn_wc__wq_build_file_move(work_item, db, dst_abspath,
156299425Smm                                     dst_tmp_abspath, dst_abspath,
157299425Smm                                     result_pool, scratch_pool));
158299425Smm
159299425Smm  return SVN_NO_ERROR;
160299425Smm}
161299425Smm
162299425Smm/* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB.
163299425Smm   If METADATA_ONLY is true, copy only the versioned metadata,
164299425Smm   otherwise copy both the versioned metadata and the filesystem node (even
165299425Smm   if it is the wrong kind, and recursively if it is a dir).
166299425Smm
167299425Smm   If IS_MOVE is true, record move information in working copy meta
168299425Smm   data in addition to copying the file.
169299425Smm
170299425Smm   If the versioned file has a text conflict, and the .mine file exists in
171299425Smm   the filesystem, copy the .mine file to DST_ABSPATH.  Otherwise, copy the
172299425Smm   versioned file itself.
173299425Smm
174299425Smm   This also works for versioned symlinks that are stored in the db as
175299425Smm   svn_node_file with svn:special set. */
176299425Smmstatic svn_error_t *
177299425Smmcopy_versioned_file(svn_wc__db_t *db,
178299425Smm                    const char *src_abspath,
179299425Smm                    const char *dst_abspath,
180299425Smm                    const char *dst_op_root_abspath,
181299425Smm                    const char *tmpdir_abspath,
182299425Smm                    svn_boolean_t metadata_only,
183299425Smm                    svn_boolean_t conflicted,
184299425Smm                    svn_boolean_t is_move,
185299425Smm                    svn_cancel_func_t cancel_func,
186299425Smm                    void *cancel_baton,
187299425Smm                    svn_wc_notify_func2_t notify_func,
188299425Smm                    void *notify_baton,
189299425Smm                    apr_pool_t *scratch_pool)
190299425Smm{
191299425Smm  svn_skel_t *work_items = NULL;
192299425Smm
193299425Smm  /* In case we are copying from one WC to another (e.g. an external dir),
194299425Smm     ensure the destination WC has a copy of the pristine text. */
195299425Smm
196299425Smm  /* Prepare a temp copy of the filesystem node.  It is usually a file, but
197299425Smm     copy recursively if it's a dir. */
198299425Smm  if (!metadata_only)
199299425Smm    {
200299425Smm      const char *my_src_abspath = NULL;
201299425Smm      svn_boolean_t handle_as_unversioned = FALSE;
202299425Smm
203299425Smm      /* By default, take the copy source as given. */
204299425Smm      my_src_abspath = src_abspath;
205299425Smm
206299425Smm      if (conflicted)
207299425Smm        {
208299425Smm          svn_skel_t *conflict;
209299425Smm          const char *conflict_working;
210299425Smm          svn_error_t *err;
211299425Smm
212299425Smm          /* Is there a text conflict at the source path? */
213299425Smm          SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath,
214299425Smm                                         scratch_pool, scratch_pool));
215299425Smm
216299425Smm          err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL,
217299425Smm                                                    db, src_abspath, conflict,
218299425Smm                                                    scratch_pool,
219299425Smm                                                    scratch_pool);
220299425Smm
221299425Smm          if (err && err->apr_err == SVN_ERR_WC_MISSING)
222299425Smm            {
223299425Smm              /* not text conflicted */
224299425Smm              svn_error_clear(err);
225299425Smm              conflict_working = NULL;
226299425Smm            }
227316338Smm          else
228299425Smm            SVN_ERR(err);
229299425Smm
230299425Smm          if (conflict_working)
231299425Smm            {
232299425Smm              svn_node_kind_t working_kind;
233299425Smm
234299425Smm              /* Does the ".mine" file exist? */
235299425Smm              SVN_ERR(svn_io_check_path(conflict_working, &working_kind,
236299425Smm                                        scratch_pool));
237299425Smm
238299425Smm              if (working_kind == svn_node_file)
239299425Smm                {
240299425Smm                   /* Don't perform unmodified/pristine optimization */
241299425Smm                  handle_as_unversioned = TRUE;
242299425Smm                  my_src_abspath = conflict_working;
243299425Smm                }
244299425Smm            }
245299425Smm        }
246299425Smm
247299425Smm      SVN_ERR(copy_to_tmpdir(&work_items, NULL, db, my_src_abspath,
248299425Smm                             dst_abspath, tmpdir_abspath,
249299425Smm                             TRUE /* file_copy */,
250299425Smm                             handle_as_unversioned /* unversioned */,
251299425Smm                             cancel_func, cancel_baton,
252299425Smm                             scratch_pool, scratch_pool));
253299425Smm    }
254299425Smm
255299425Smm  /* Copy the (single) node's metadata, and move the new filesystem node
256299425Smm     into place. */
257299425Smm  SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
258299425Smm                             dst_op_root_abspath, is_move, work_items,
259299425Smm                             scratch_pool));
260299425Smm
261299425Smm  if (notify_func)
262299425Smm    {
263299425Smm      svn_wc_notify_t *notify
264299425Smm        = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
265299425Smm                               scratch_pool);
266299425Smm      notify->kind = svn_node_file;
267299425Smm
268299425Smm      /* When we notify that we performed a copy, make sure we already did */
269299425Smm      if (work_items != NULL)
270299425Smm        SVN_ERR(svn_wc__wq_run(db, dst_abspath,
271299425Smm                               cancel_func, cancel_baton, scratch_pool));
272299425Smm      (*notify_func)(notify_baton, notify, scratch_pool);
273299425Smm    }
274299425Smm  return SVN_NO_ERROR;
275299425Smm}
276299425Smm
277299425Smm/* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB,
278299425Smm   recursively.  If METADATA_ONLY is true, copy only the versioned metadata,
279299425Smm   otherwise copy both the versioned metadata and the filesystem nodes (even
280299425Smm   if they are the wrong kind, and including unversioned children).
281299425Smm   If IS_MOVE is true, record move information in working copy meta
282299425Smm   data in addition to copying the directory.
283299425Smm
284299425Smm   WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root)
285299425Smm */
286299425Smmstatic svn_error_t *
287299425Smmcopy_versioned_dir(svn_wc__db_t *db,
288299425Smm                   const char *src_abspath,
289299425Smm                   const char *dst_abspath,
290299425Smm                   const char *dst_op_root_abspath,
291299425Smm                   const char *tmpdir_abspath,
292299425Smm                   svn_boolean_t metadata_only,
293299425Smm                   svn_boolean_t is_move,
294299425Smm                   svn_cancel_func_t cancel_func,
295299425Smm                   void *cancel_baton,
296299425Smm                   svn_wc_notify_func2_t notify_func,
297299425Smm                   void *notify_baton,
298299425Smm                   apr_pool_t *scratch_pool)
299299425Smm{
300299425Smm  svn_skel_t *work_items = NULL;
301299425Smm  const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
302299425Smm  apr_hash_t *versioned_children;
303299425Smm  apr_hash_t *conflicted_children;
304299425Smm  apr_hash_t *disk_children;
305299425Smm  apr_hash_index_t *hi;
306299425Smm  svn_node_kind_t disk_kind;
307299425Smm  apr_pool_t *iterpool;
308299425Smm
309299425Smm  /* Prepare a temp copy of the single filesystem node (usually a dir). */
310299425Smm  if (!metadata_only)
311299425Smm    {
312299425Smm      SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind,
313299425Smm                             db, src_abspath, dst_abspath,
314299425Smm                             tmpdir_abspath,
315299425Smm                             FALSE /* file_copy */,
316299425Smm                             FALSE /* unversioned */,
317299425Smm                             cancel_func, cancel_baton,
318299425Smm                             scratch_pool, scratch_pool));
319299425Smm    }
320299425Smm
321299425Smm  /* Copy the (single) node's metadata, and move the new filesystem node
322299425Smm     into place. */
323299425Smm  SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
324299425Smm                             dst_op_root_abspath, is_move, work_items,
325299425Smm                             scratch_pool));
326299425Smm
327299425Smm  if (notify_func)
328299425Smm    {
329299425Smm      svn_wc_notify_t *notify
330299425Smm        = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
331299425Smm                               scratch_pool);
332299425Smm      notify->kind = svn_node_dir;
333299425Smm
334299425Smm      /* When we notify that we performed a copy, make sure we already did */
335299425Smm      if (work_items != NULL)
336299425Smm        SVN_ERR(svn_wc__wq_run(db, dir_abspath,
337299425Smm                               cancel_func, cancel_baton, scratch_pool));
338299425Smm
339299425Smm      (*notify_func)(notify_baton, notify, scratch_pool);
340299425Smm    }
341358090Smm
342299425Smm  if (!metadata_only && disk_kind == svn_node_dir)
343299425Smm    /* All filesystem children, versioned and unversioned.  We're only
344299425Smm       interested in their names, so we can pass TRUE as the only_check_type
345299425Smm       param. */
346299425Smm    SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE,
347299425Smm                                scratch_pool, scratch_pool));
348299425Smm  else
349299425Smm    disk_children = NULL;
350299425Smm
351299425Smm  /* Copy all the versioned children */
352299425Smm  iterpool = svn_pool_create(scratch_pool);
353299425Smm  SVN_ERR(svn_wc__db_read_children_info(&versioned_children,
354299425Smm                                        &conflicted_children,
355299425Smm                                        db, src_abspath,
356299425Smm                                        scratch_pool, iterpool));
357299425Smm  for (hi = apr_hash_first(scratch_pool, versioned_children);
358299425Smm       hi;
359299425Smm       hi = apr_hash_next(hi))
360299425Smm    {
361299425Smm      const char *child_name, *child_src_abspath, *child_dst_abspath;
362299425Smm      struct svn_wc__db_info_t *info;
363299425Smm
364358090Smm      svn_pool_clear(iterpool);
365299425Smm
366299425Smm      if (cancel_func)
367299425Smm        SVN_ERR(cancel_func(cancel_baton));
368299425Smm
369299425Smm      child_name = svn__apr_hash_index_key(hi);
370299425Smm      info = svn__apr_hash_index_val(hi);
371299425Smm      child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool);
372299425Smm      child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool);
373299425Smm
374299425Smm      if (info->op_root)
375299425Smm        SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db,
376299425Smm                                                  child_src_abspath,
377299425Smm                                                  child_dst_abspath,
378299425Smm                                                  is_move,
379299425Smm                                                  scratch_pool));
380299425Smm
381299425Smm      if (info->status == svn_wc__db_status_normal
382299425Smm          || info->status == svn_wc__db_status_added)
383299425Smm        {
384299425Smm          /* We have more work to do than just changing the DB */
385299425Smm          if (info->kind == svn_node_file)
386299425Smm            {
387299425Smm              /* We should skip this node if this child is a file external
388299425Smm                 (issues #3589, #4000) */
389299425Smm              if (!info->file_external)
390299425Smm                SVN_ERR(copy_versioned_file(db,
391299425Smm                                            child_src_abspath,
392299425Smm                                            child_dst_abspath,
393299425Smm                                            dst_op_root_abspath,
394299425Smm                                            tmpdir_abspath,
395299425Smm                                            metadata_only, info->conflicted,
396299425Smm                                            is_move,
397299425Smm                                            cancel_func, cancel_baton,
398299425Smm                                            NULL, NULL,
399299425Smm                                            iterpool));
400299425Smm            }
401299425Smm          else if (info->kind == svn_node_dir)
402299425Smm            SVN_ERR(copy_versioned_dir(db,
403299425Smm                                       child_src_abspath, child_dst_abspath,
404299425Smm                                       dst_op_root_abspath, tmpdir_abspath,
405299425Smm                                       metadata_only, is_move,
406299425Smm                                       cancel_func, cancel_baton, NULL, NULL,
407299425Smm                                       iterpool));
408299425Smm          else
409299425Smm            return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
410299425Smm                                     _("cannot handle node kind for '%s'"),
411299425Smm                                     svn_dirent_local_style(child_src_abspath,
412299425Smm                                                            scratch_pool));
413299425Smm        }
414299425Smm      else if (info->status == svn_wc__db_status_deleted
415299425Smm          || info->status == svn_wc__db_status_not_present
416299425Smm          || info->status == svn_wc__db_status_excluded)
417299425Smm        {
418299425Smm          /* This will be copied as some kind of deletion. Don't touch
419299425Smm             any actual files */
420299425Smm          SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath,
421299425Smm                                     child_dst_abspath, dst_op_root_abspath,
422299425Smm                                     is_move, NULL, iterpool));
423299425Smm
424299425Smm          /* Don't recurse on children while all we do is creating not-present
425299425Smm             children */
426299425Smm        }
427299425Smm      else if (info->status == svn_wc__db_status_incomplete)
428299425Smm        {
429299425Smm          /* Should go ahead and copy incomplete to incomplete? Try to
430299425Smm             copy as much as possible, or give up early? */
431299425Smm          return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
432299425Smm                                   _("Cannot handle status of '%s'"),
433299425Smm                                   svn_dirent_local_style(child_src_abspath,
434299425Smm                                                          iterpool));
435299425Smm        }
436299425Smm      else
437299425Smm        {
438299425Smm          SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded);
439299425Smm
440299425Smm          return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
441299425Smm                                   _("Cannot copy '%s' excluded by server"),
442299425Smm                                   svn_dirent_local_style(child_src_abspath,
443299425Smm                                                          iterpool));
444299425Smm        }
445299425Smm
446299425Smm      if (disk_children
447299425Smm          && (info->status == svn_wc__db_status_normal
448299425Smm              || info->status == svn_wc__db_status_added))
449299425Smm        {
450299425Smm          /* Remove versioned child as it has been handled */
451299425Smm          svn_hash_sets(disk_children, child_name, NULL);
452299425Smm        }
453299425Smm    }
454299425Smm
455299425Smm  /* Copy the remaining filesystem children, which are unversioned, skipping
456299425Smm     any conflict-marker files. */
457299425Smm  if (disk_children && apr_hash_count(disk_children))
458299425Smm    {
459299425Smm      apr_hash_t *marker_files;
460299425Smm
461299425Smm      SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db,
462299425Smm                                                   src_abspath, scratch_pool,
463299425Smm                                                   scratch_pool));
464299425Smm
465299425Smm      work_items = NULL;
466299425Smm
467299425Smm      for (hi = apr_hash_first(scratch_pool, disk_children); hi;
468299425Smm           hi = apr_hash_next(hi))
469299425Smm        {
470299425Smm          const char *name = svn__apr_hash_index_key(hi);
471299425Smm          const char *unver_src_abspath, *unver_dst_abspath;
472299425Smm          svn_skel_t *work_item;
473299425Smm
474299425Smm          if (svn_wc_is_adm_dir(name, iterpool))
475299425Smm            continue;
476299425Smm
477299425Smm          if (cancel_func)
478299425Smm            SVN_ERR(cancel_func(cancel_baton));
479299425Smm
480299425Smm          svn_pool_clear(iterpool);
481299425Smm          unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool);
482299425Smm          unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool);
483299425Smm
484299425Smm          if (marker_files &&
485299425Smm              svn_hash_gets(marker_files, unver_src_abspath))
486299425Smm            continue;
487299425Smm
488299425Smm          SVN_ERR(copy_to_tmpdir(&work_item, NULL, db, unver_src_abspath,
489299425Smm                                 unver_dst_abspath, tmpdir_abspath,
490299425Smm                                 TRUE /* recursive */, TRUE /* unversioned */,
491299425Smm                                 cancel_func, cancel_baton,
492299425Smm                                 scratch_pool, iterpool));
493299425Smm
494299425Smm          if (work_item)
495299425Smm            work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
496299425Smm        }
497299425Smm      SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool));
498299425Smm    }
499299425Smm
500299425Smm  svn_pool_destroy(iterpool);
501299425Smm
502299425Smm  return SVN_NO_ERROR;
503299425Smm}
504299425Smm
505299425Smm
506299425Smm/* The guts of svn_wc_copy3() and svn_wc_move().
507299425Smm * The additional parameter IS_MOVE indicates whether this is a copy or
508299425Smm * a move operation.
509299425Smm *
510299425Smm * If MOVE_DEGRADED_TO_COPY is not NULL and a move had to be degraded
511299425Smm * to a copy, then set *MOVE_DEGRADED_TO_COPY. */
512299425Smmstatic svn_error_t *
513299425Smmcopy_or_move(svn_boolean_t *move_degraded_to_copy,
514299425Smm             svn_wc_context_t *wc_ctx,
515299425Smm             const char *src_abspath,
516299425Smm             const char *dst_abspath,
517299425Smm             svn_boolean_t metadata_only,
518299425Smm             svn_boolean_t is_move,
519311042Smm             svn_boolean_t allow_mixed_revisions,
520299425Smm             svn_cancel_func_t cancel_func,
521299425Smm             void *cancel_baton,
522299425Smm             svn_wc_notify_func2_t notify_func,
523299425Smm             void *notify_baton,
524299425Smm             apr_pool_t *scratch_pool)
525299425Smm{
526299425Smm  svn_wc__db_t *db = wc_ctx->db;
527299425Smm  svn_node_kind_t src_db_kind;
528299425Smm  const char *dstdir_abspath;
529299425Smm  svn_boolean_t conflicted;
530299425Smm  const char *tmpdir_abspath;
531299425Smm  const char *src_wcroot_abspath;
532299425Smm  const char *dst_wcroot_abspath;
533299425Smm  svn_boolean_t within_one_wc;
534299425Smm  svn_wc__db_status_t src_status;
535299425Smm  svn_error_t *err;
536299425Smm
537299425Smm  SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
538299425Smm  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
539299425Smm
540299425Smm  dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
541299425Smm
542299425Smm  /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH;
543299425Smm     throw an error if not. */
544299425Smm  {
545299425Smm    svn_wc__db_status_t dstdir_status;
546299425Smm    const char *src_repos_root_url, *dst_repos_root_url;
547299425Smm    const char *src_repos_uuid, *dst_repos_uuid;
548299425Smm    const char *src_repos_relpath;
549299425Smm
550299425Smm    err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL,
551299425Smm                               &src_repos_relpath, &src_repos_root_url,
552299425Smm                               &src_repos_uuid, NULL, NULL, NULL, NULL, NULL,
553299425Smm                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
554299425Smm                               NULL, &conflicted, NULL, NULL, NULL, NULL,
555299425Smm                               NULL, NULL,
556299425Smm                               db, src_abspath, scratch_pool, scratch_pool);
557299425Smm
558299425Smm    if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
559299425Smm      {
560299425Smm        /* Replicate old error code and text */
561299425Smm        svn_error_clear(err);
562299425Smm        return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
563299425Smm                                 _("'%s' is not under version control"),
564299425Smm                                 svn_dirent_local_style(src_abspath,
565299425Smm                                                        scratch_pool));
566299425Smm      }
567299425Smm    else
568299425Smm      SVN_ERR(err);
569299425Smm
570299425Smm    /* Do this now, as we know the right data is cached */
571299425Smm    SVN_ERR(svn_wc__db_get_wcroot(&src_wcroot_abspath, db, src_abspath,
572299425Smm                                  scratch_pool, scratch_pool));
573299425Smm
574299425Smm    switch (src_status)
575299425Smm      {
576299425Smm        case svn_wc__db_status_deleted:
577299425Smm          return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
578299425Smm                                   _("Deleted node '%s' can't be copied."),
579299425Smm                                   svn_dirent_local_style(src_abspath,
580299425Smm                                                          scratch_pool));
581299425Smm
582299425Smm        case svn_wc__db_status_excluded:
583299425Smm        case svn_wc__db_status_server_excluded:
584299425Smm        case svn_wc__db_status_not_present:
585299425Smm          return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
586299425Smm                                   _("The node '%s' was not found."),
587299425Smm                                   svn_dirent_local_style(src_abspath,
588299425Smm                                                          scratch_pool));
589299425Smm        default:
590299425Smm          break;
591299425Smm      }
592299425Smm
593299425Smm     if (is_move && ! strcmp(src_abspath, src_wcroot_abspath))
594299425Smm      {
595299425Smm        return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
596299425Smm                                 _("'%s' is the root of a working copy and "
597299425Smm                                   "cannot be moved"),
598299425Smm                                   svn_dirent_local_style(src_abspath,
599299425Smm                                                          scratch_pool));
600299425Smm      }
601299425Smm    if (is_move && src_repos_relpath && !src_repos_relpath[0])
602299425Smm      {
603299425Smm        return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
604311042Smm                                 _("'%s' represents the repository root "
605299425Smm                                   "and cannot be moved"),
606299425Smm                                 svn_dirent_local_style(src_abspath,
607299425Smm                                                        scratch_pool));
608299425Smm      }
609299425Smm
610299425Smm    err = svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL,
611299425Smm                               &dst_repos_root_url, &dst_repos_uuid, NULL,
612299425Smm                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
613299425Smm                               NULL, NULL, NULL, NULL, NULL, NULL,
614299425Smm                               NULL, NULL, NULL, NULL,
615299425Smm                               NULL, NULL, NULL,
616299425Smm                               db, dstdir_abspath,
617299425Smm                               scratch_pool, scratch_pool);
618299425Smm
619299425Smm    if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
620299425Smm      {
621299425Smm        /* An unversioned destination directory exists on disk. */
622299425Smm        svn_error_clear(err);
623299425Smm        return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
624299425Smm                                 _("'%s' is not under version control"),
625299425Smm                                 svn_dirent_local_style(dstdir_abspath,
626299425Smm                                                        scratch_pool));
627299425Smm      }
628299425Smm    else
629299425Smm      SVN_ERR(err);
630299425Smm
631299425Smm    /* Do this now, as we know the right data is cached */
632299425Smm    SVN_ERR(svn_wc__db_get_wcroot(&dst_wcroot_abspath, db, dstdir_abspath,
633299425Smm                                  scratch_pool, scratch_pool));
634299425Smm
635299425Smm    if (!src_repos_root_url)
636299425Smm      {
637299425Smm        if (src_status == svn_wc__db_status_added)
638299425Smm          SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
639299425Smm                                           &src_repos_root_url,
640299425Smm                                           &src_repos_uuid, NULL, NULL, NULL,
641299425Smm                                           NULL,
642299425Smm                                           db, src_abspath,
643299425Smm                                           scratch_pool, scratch_pool));
644299425Smm        else
645299425Smm          /* If not added, the node must have a base or we can't copy */
646299425Smm          SVN_ERR(svn_wc__db_scan_base_repos(NULL, &src_repos_root_url,
647299425Smm                                             &src_repos_uuid,
648299425Smm                                             db, src_abspath,
649299425Smm                                             scratch_pool, scratch_pool));
650299425Smm      }
651299425Smm
652299425Smm    if (!dst_repos_root_url)
653299425Smm      {
654299425Smm        if (dstdir_status == svn_wc__db_status_added)
655299425Smm          SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
656299425Smm                                           &dst_repos_root_url,
657299425Smm                                           &dst_repos_uuid, NULL, NULL, NULL,
658299425Smm                                           NULL,
659299425Smm                                           db, dstdir_abspath,
660299425Smm                                           scratch_pool, scratch_pool));
661299425Smm        else
662299425Smm          /* If not added, the node must have a base or we can't copy */
663299425Smm          SVN_ERR(svn_wc__db_scan_base_repos(NULL, &dst_repos_root_url,
664299425Smm                                             &dst_repos_uuid,
665299425Smm                                             db, dstdir_abspath,
666299425Smm                                             scratch_pool, scratch_pool));
667299425Smm      }
668299425Smm
669299425Smm    if (strcmp(src_repos_root_url, dst_repos_root_url) != 0
670299425Smm        || strcmp(src_repos_uuid, dst_repos_uuid) != 0)
671299425Smm      return svn_error_createf(
672299425Smm         SVN_ERR_WC_INVALID_SCHEDULE, NULL,
673299425Smm         _("Cannot copy to '%s', as it is not from repository '%s'; "
674299425Smm           "it is from '%s'"),
675299425Smm         svn_dirent_local_style(dst_abspath, scratch_pool),
676299425Smm         src_repos_root_url, dst_repos_root_url);
677299425Smm
678299425Smm    if (dstdir_status == svn_wc__db_status_deleted)
679299425Smm      return svn_error_createf(
680299425Smm         SVN_ERR_WC_INVALID_SCHEDULE, NULL,
681299425Smm         _("Cannot copy to '%s' as it is scheduled for deletion"),
682299425Smm         svn_dirent_local_style(dst_abspath, scratch_pool));
683299425Smm         /* ### should report dstdir_abspath instead of dst_abspath? */
684299425Smm  }
685299425Smm
686299425Smm  /* TODO(#2843): Rework the error report. */
687299425Smm  /* Check if the copy target is missing or hidden and thus not exist on the
688299425Smm     disk, before actually doing the file copy. */
689299425Smm  {
690299425Smm    svn_wc__db_status_t dst_status;
691299425Smm
692299425Smm    err = svn_wc__db_read_info(&dst_status, NULL, NULL, NULL, NULL, NULL,
693299425Smm                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
694299425Smm                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
695299425Smm                               NULL, NULL, NULL, NULL, NULL,
696299425Smm                               db, dst_abspath, scratch_pool, scratch_pool);
697299425Smm
698299425Smm    if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
699299425Smm      return svn_error_trace(err);
700299425Smm
701    svn_error_clear(err);
702
703    if (!err)
704      switch (dst_status)
705        {
706          case svn_wc__db_status_excluded:
707            return svn_error_createf(
708                     SVN_ERR_ENTRY_EXISTS, NULL,
709                     _("'%s' is already under version control "
710                       "but is excluded."),
711                     svn_dirent_local_style(dst_abspath, scratch_pool));
712          case svn_wc__db_status_server_excluded:
713            return svn_error_createf(
714                     SVN_ERR_ENTRY_EXISTS, NULL,
715                     _("'%s' is already under version control"),
716                     svn_dirent_local_style(dst_abspath, scratch_pool));
717
718          case svn_wc__db_status_deleted:
719          case svn_wc__db_status_not_present:
720            break; /* OK to add */
721
722          default:
723            return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
724                               _("There is already a versioned item '%s'"),
725                               svn_dirent_local_style(dst_abspath,
726                                                      scratch_pool));
727        }
728  }
729
730  /* Check that the target path is not obstructed, if required. */
731  if (!metadata_only)
732    {
733      svn_node_kind_t dst_kind;
734
735      /* (We need only to check the root of the copy, not every path inside
736         copy_versioned_file/_dir.) */
737      SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind, scratch_pool));
738      if (dst_kind != svn_node_none)
739        return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
740                                 _("'%s' already exists and is in the way"),
741                                 svn_dirent_local_style(dst_abspath,
742                                                        scratch_pool));
743    }
744
745  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
746                                         dstdir_abspath,
747                                         scratch_pool, scratch_pool));
748
749  within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0);
750
751  if (is_move
752      && !within_one_wc)
753    {
754      if (move_degraded_to_copy)
755        *move_degraded_to_copy = TRUE;
756
757      is_move = FALSE;
758    }
759
760  if (!within_one_wc)
761    SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath,
762                                         cancel_func, cancel_baton,
763                                         scratch_pool));
764
765  if (src_db_kind == svn_node_file
766      || src_db_kind == svn_node_symlink)
767    {
768      err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath,
769                                tmpdir_abspath,
770                                metadata_only, conflicted, is_move,
771                                cancel_func, cancel_baton,
772                                notify_func, notify_baton,
773                                scratch_pool);
774    }
775  else
776    {
777      if (is_move
778          && src_status == svn_wc__db_status_normal)
779        {
780          svn_revnum_t min_rev;
781          svn_revnum_t max_rev;
782
783          /* Verify that the move source is a single-revision subtree. */
784          SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db,
785                                               src_abspath, FALSE, scratch_pool));
786          if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) &&
787              min_rev != max_rev)
788            {
789              if (!allow_mixed_revisions)
790                return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL,
791                                         _("Cannot move mixed-revision "
792                                           "subtree '%s' [%ld:%ld]; "
793                                           "try updating it first"),
794                                         svn_dirent_local_style(src_abspath,
795                                                                scratch_pool),
796                                         min_rev, max_rev);
797
798              is_move = FALSE;
799              if (move_degraded_to_copy)
800                *move_degraded_to_copy = TRUE;
801            }
802        }
803
804      err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath,
805                               tmpdir_abspath, metadata_only, is_move,
806                               cancel_func, cancel_baton,
807                               notify_func, notify_baton,
808                               scratch_pool);
809    }
810
811  if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED))
812    return svn_error_trace(err);
813
814  if (is_move)
815    err = svn_error_compose_create(err,
816                svn_wc__db_op_handle_move_back(NULL,
817                                               db, dst_abspath, src_abspath,
818                                               NULL /* work_items */,
819                                               scratch_pool));
820
821  /* Run the work queue with the remaining work */
822  SVN_ERR(svn_error_compose_create(
823                                err,
824                                svn_wc__wq_run(db, dst_abspath,
825                                                   cancel_func, cancel_baton,
826                                                   scratch_pool)));
827
828  return SVN_NO_ERROR;
829}
830
831
832/* Public Interface */
833
834svn_error_t *
835svn_wc_copy3(svn_wc_context_t *wc_ctx,
836             const char *src_abspath,
837             const char *dst_abspath,
838             svn_boolean_t metadata_only,
839             svn_cancel_func_t cancel_func,
840             void *cancel_baton,
841             svn_wc_notify_func2_t notify_func,
842             void *notify_baton,
843             apr_pool_t *scratch_pool)
844{
845  /* Verify that we have the required write lock. */
846  SVN_ERR(svn_wc__write_check(wc_ctx->db,
847                              svn_dirent_dirname(dst_abspath, scratch_pool),
848                              scratch_pool));
849
850  return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath,
851                                      metadata_only, FALSE /* is_move */,
852                                      TRUE /* allow_mixed_revisions */,
853                                      cancel_func, cancel_baton,
854                                      notify_func, notify_baton,
855                                      scratch_pool));
856}
857
858
859/* Remove the conflict markers of NODE_ABSPATH, that were left over after
860   copying NODE_ABSPATH from SRC_ABSPATH.
861
862   Only use this function when you know what you're doing. This function
863   explicitly ignores some case insensitivity issues!
864
865   */
866static svn_error_t *
867remove_node_conflict_markers(svn_wc__db_t *db,
868                             const char *src_abspath,
869                             const char *node_abspath,
870                             apr_pool_t *scratch_pool)
871{
872  svn_skel_t *conflict;
873
874  SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath,
875                                   scratch_pool, scratch_pool));
876
877  /* Do we have conflict markers that should be removed? */
878  if (conflict != NULL)
879    {
880      const apr_array_header_t *markers;
881      int i;
882      const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool);
883      const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool);
884
885      SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath,
886                                            conflict,
887                                            scratch_pool, scratch_pool));
888
889      /* No iterpool: Maximum number of possible conflict markers is 4 */
890      for (i = 0; markers && (i < markers->nelts); i++)
891        {
892          const char *marker_abspath;
893          const char *child_relpath;
894          const char *child_abspath;
895
896          marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
897
898          child_relpath = svn_dirent_skip_ancestor(src_dir, marker_abspath);
899
900          if (child_relpath)
901            {
902              child_abspath = svn_dirent_join(dst_dir, child_relpath,
903                                              scratch_pool);
904
905              SVN_ERR(svn_io_remove_file2(child_abspath, TRUE, scratch_pool));
906            }
907        }
908    }
909
910  return SVN_NO_ERROR;
911}
912
913/* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over
914   after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH.
915
916   This function doesn't remove the conflict markers on WC_DIR_ABSPATH
917   itself!
918
919   Only use this function when you know what you're doing. This function
920   explicitly ignores some case insensitivity issues!
921   */
922static svn_error_t *
923remove_all_conflict_markers(svn_wc__db_t *db,
924                            const char *src_dir_abspath,
925                            const char *dst_dir_abspath,
926                            apr_pool_t *scratch_pool)
927{
928  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
929  apr_hash_t *nodes;
930  apr_hash_t *conflicts; /* Unused */
931  apr_hash_index_t *hi;
932
933  /* Reuse a status helper to obtain all subdirs and conflicts in a single
934     db transaction. */
935  /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy
936          artillery. */
937  SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db,
938                                        src_dir_abspath,
939                                        scratch_pool, iterpool));
940
941  for (hi = apr_hash_first(scratch_pool, nodes);
942       hi;
943       hi = apr_hash_next(hi))
944    {
945      const char *name = svn__apr_hash_index_key(hi);
946      struct svn_wc__db_info_t *info = svn__apr_hash_index_val(hi);
947
948      if (info->conflicted)
949        {
950          svn_pool_clear(iterpool);
951          SVN_ERR(remove_node_conflict_markers(
952                            db,
953                            svn_dirent_join(src_dir_abspath, name, iterpool),
954                            svn_dirent_join(dst_dir_abspath, name, iterpool),
955                            iterpool));
956        }
957      if (info->kind == svn_node_dir)
958        {
959          svn_pool_clear(iterpool);
960          SVN_ERR(remove_all_conflict_markers(
961                            db,
962                            svn_dirent_join(src_dir_abspath, name, iterpool),
963                            svn_dirent_join(dst_dir_abspath, name, iterpool),
964                            iterpool));
965        }
966    }
967
968  svn_pool_destroy(iterpool);
969  return SVN_NO_ERROR;
970}
971
972svn_error_t *
973svn_wc__move2(svn_wc_context_t *wc_ctx,
974              const char *src_abspath,
975              const char *dst_abspath,
976              svn_boolean_t metadata_only,
977              svn_boolean_t allow_mixed_revisions,
978              svn_cancel_func_t cancel_func,
979              void *cancel_baton,
980              svn_wc_notify_func2_t notify_func,
981              void *notify_baton,
982              apr_pool_t *scratch_pool)
983{
984  svn_wc__db_t *db = wc_ctx->db;
985  svn_boolean_t move_degraded_to_copy = FALSE;
986  svn_node_kind_t kind;
987  svn_boolean_t conflicted;
988
989  /* Verify that we have the required write locks. */
990  SVN_ERR(svn_wc__write_check(wc_ctx->db,
991                              svn_dirent_dirname(src_abspath, scratch_pool),
992                              scratch_pool));
993  SVN_ERR(svn_wc__write_check(wc_ctx->db,
994                              svn_dirent_dirname(dst_abspath, scratch_pool),
995                              scratch_pool));
996
997  SVN_ERR(copy_or_move(&move_degraded_to_copy,
998                       wc_ctx, src_abspath, dst_abspath,
999                       TRUE /* metadata_only */,
1000                       TRUE /* is_move */,
1001                       allow_mixed_revisions,
1002                       cancel_func, cancel_baton,
1003                       notify_func, notify_baton,
1004                       scratch_pool));
1005
1006  /* An interrupt at this point will leave the new copy marked as
1007     moved-here but the source has not yet been deleted or marked as
1008     moved-to. */
1009
1010  /* Should we be using a workqueue for this move?  It's not clear.
1011     What should happen if the copy above is interrupted?  The user
1012     may want to abort the move and a workqueue might interfere with
1013     that.
1014
1015     BH: On Windows it is not unlikely to encounter an access denied on
1016     this line. Installing the move in the workqueue via the copy_or_move
1017     might make it hard to recover from that situation, while the DB
1018     is still in a valid state. So be careful when switching this over
1019     to the workqueue. */
1020  if (!metadata_only)
1021    SVN_ERR(svn_io_file_rename(src_abspath, dst_abspath, scratch_pool));
1022
1023  SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL,
1024                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1025                               NULL, NULL, NULL, NULL, NULL, NULL,
1026                               &conflicted, NULL, NULL, NULL,
1027                               NULL, NULL, NULL,
1028                               db, src_abspath,
1029                               scratch_pool, scratch_pool));
1030
1031  if (kind == svn_node_dir)
1032    SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath,
1033                                        scratch_pool));
1034
1035  if (conflicted)
1036    {
1037      /* When we moved a directory, we moved the conflict markers
1038         with the target... if we moved a file we only moved the
1039         file itself and the markers are still in the old location */
1040      SVN_ERR(remove_node_conflict_markers(db, src_abspath,
1041                                           (kind == svn_node_dir)
1042                                             ? dst_abspath
1043                                             : src_abspath,
1044                                           scratch_pool));
1045    }
1046
1047  SVN_ERR(svn_wc__db_op_delete(db, src_abspath,
1048                               move_degraded_to_copy ? NULL : dst_abspath,
1049                               TRUE /* delete_dir_externals */,
1050                               NULL /* conflict */, NULL /* work_items */,
1051                               cancel_func, cancel_baton,
1052                               notify_func, notify_baton,
1053                               scratch_pool));
1054
1055  return SVN_NO_ERROR;
1056}
1057