1251881Speter/*
2251881Speter * copy.c:  wc 'copy' functionality.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include <string.h>
31251881Speter#include "svn_pools.h"
32251881Speter#include "svn_error.h"
33251881Speter#include "svn_dirent_uri.h"
34251881Speter#include "svn_path.h"
35251881Speter#include "svn_hash.h"
36251881Speter
37251881Speter#include "wc.h"
38251881Speter#include "workqueue.h"
39251881Speter#include "props.h"
40251881Speter#include "conflicts.h"
41251881Speter
42251881Speter#include "svn_private_config.h"
43251881Speter#include "private/svn_wc_private.h"
44251881Speter
45289180Speter/* #define RECORD_MIXED_MOVE */
46251881Speter
47251881Speter/*** Code. ***/
48251881Speter
49251881Speter/* Make a copy of the filesystem node (or tree if RECURSIVE) at
50251881Speter   SRC_ABSPATH under a temporary name in the directory
51251881Speter   TMPDIR_ABSPATH and return the absolute path of the copy in
52251881Speter   *DST_ABSPATH.  Return the node kind of SRC_ABSPATH in *KIND.  If
53251881Speter   SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate
54289180Speter   that no copy was made.
55289180Speter
56289180Speter   If DIRENT is not NULL, it contains the on-disk information of SRC_ABSPATH.
57289180Speter   RECORDED_SIZE (if not SVN_INVALID_FILESIZE) contains the recorded size of
58289180Speter   SRC_ABSPATH, and RECORDED_TIME the recorded size or 0.
59289180Speter
60289180Speter   These values will be used to avoid unneeded work.
61289180Speter */
62251881Speterstatic svn_error_t *
63251881Spetercopy_to_tmpdir(svn_skel_t **work_item,
64251881Speter               svn_node_kind_t *kind,
65251881Speter               svn_wc__db_t *db,
66251881Speter               const char *src_abspath,
67251881Speter               const char *dst_abspath,
68251881Speter               const char *tmpdir_abspath,
69251881Speter               svn_boolean_t file_copy,
70251881Speter               svn_boolean_t unversioned,
71289180Speter               const svn_io_dirent2_t *dirent,
72289180Speter               svn_filesize_t recorded_size,
73289180Speter               apr_time_t recorded_time,
74251881Speter               svn_cancel_func_t cancel_func,
75251881Speter               void *cancel_baton,
76251881Speter               apr_pool_t *result_pool,
77251881Speter               apr_pool_t *scratch_pool)
78251881Speter{
79251881Speter  svn_boolean_t is_special;
80251881Speter  svn_io_file_del_t delete_when;
81251881Speter  const char *dst_tmp_abspath;
82251881Speter  svn_node_kind_t dsk_kind;
83251881Speter  if (!kind)
84251881Speter    kind = &dsk_kind;
85251881Speter
86251881Speter  *work_item = NULL;
87251881Speter
88289180Speter  if (dirent)
89289180Speter    {
90289180Speter      *kind = dirent->kind;
91289180Speter      is_special = dirent->special;
92289180Speter    }
93289180Speter  else
94289180Speter    SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special,
95289180Speter                                      scratch_pool));
96251881Speter  if (*kind == svn_node_none)
97251881Speter    {
98251881Speter      return SVN_NO_ERROR;
99251881Speter    }
100251881Speter  else if (*kind == svn_node_unknown)
101251881Speter    {
102251881Speter      return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
103251881Speter                               _("Source '%s' is unexpected kind"),
104251881Speter                               svn_dirent_local_style(src_abspath,
105251881Speter                                                      scratch_pool));
106251881Speter    }
107251881Speter  else if (*kind == svn_node_dir || is_special)
108251881Speter    delete_when = svn_io_file_del_on_close;
109251881Speter  else /* the default case: (*kind == svn_node_file) */
110251881Speter    delete_when = svn_io_file_del_none;
111251881Speter
112251881Speter  /* ### Do we need a pool cleanup to remove the copy?  We can't use
113251881Speter     ### svn_io_file_del_on_pool_cleanup above because a) it won't
114251881Speter     ### handle the directory case and b) we need to be able to remove
115251881Speter     ### the cleanup before queueing the move work item. */
116251881Speter
117251881Speter  if (file_copy && !unversioned)
118251881Speter    {
119251881Speter      svn_boolean_t modified;
120251881Speter      /* It's faster to look for mods on the source now, as
121251881Speter         the timestamp might match, than to examine the
122251881Speter         destination later as the destination timestamp will
123251881Speter         never match. */
124289180Speter
125289180Speter      if (dirent
126289180Speter          && dirent->kind == svn_node_file
127289180Speter          && recorded_size != SVN_INVALID_FILESIZE
128289180Speter          && recorded_size == dirent->filesize
129289180Speter          && recorded_time == dirent->mtime)
130289180Speter        {
131289180Speter          modified = FALSE; /* Recorded matches on-disk. Easy out */
132289180Speter        }
133289180Speter      else
134289180Speter        {
135289180Speter          SVN_ERR(svn_wc__internal_file_modified_p(&modified, db, src_abspath,
136289180Speter                                                   FALSE, scratch_pool));
137289180Speter        }
138289180Speter
139251881Speter      if (!modified)
140251881Speter        {
141251881Speter          /* Why create a temp copy if we can just reinstall from pristine? */
142251881Speter          SVN_ERR(svn_wc__wq_build_file_install(work_item,
143251881Speter                                                db, dst_abspath, NULL, FALSE,
144251881Speter                                                TRUE,
145251881Speter                                                result_pool, scratch_pool));
146251881Speter          return SVN_NO_ERROR;
147251881Speter        }
148251881Speter    }
149289180Speter  else if (*kind == svn_node_dir && !file_copy)
150289180Speter    {
151289180Speter      /* Just build a new direcory from the workqueue */
152289180Speter      SVN_ERR(svn_wc__wq_build_dir_install(work_item,
153289180Speter                                           db, dst_abspath,
154289180Speter                                           result_pool, scratch_pool));
155251881Speter
156289180Speter      return SVN_NO_ERROR;
157289180Speter    }
158289180Speter
159251881Speter  /* Set DST_TMP_ABSPATH to a temporary unique path.  If *KIND is file, leave
160251881Speter     a file there and then overwrite it; otherwise leave no node on disk at
161251881Speter     that path.  In the latter case, something else might use that path
162251881Speter     before we get around to using it a moment later, but never mind. */
163251881Speter  SVN_ERR(svn_io_open_unique_file3(NULL, &dst_tmp_abspath, tmpdir_abspath,
164251881Speter                                   delete_when, scratch_pool, scratch_pool));
165251881Speter
166251881Speter  if (*kind == svn_node_dir)
167251881Speter    {
168251881Speter      if (file_copy)
169251881Speter        SVN_ERR(svn_io_copy_dir_recursively(
170251881Speter                           src_abspath,
171251881Speter                           tmpdir_abspath,
172251881Speter                           svn_dirent_basename(dst_tmp_abspath, scratch_pool),
173251881Speter                           TRUE, /* copy_perms */
174251881Speter                           cancel_func, cancel_baton,
175251881Speter                           scratch_pool));
176251881Speter      else
177251881Speter        SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool));
178251881Speter    }
179251881Speter  else if (!is_special)
180251881Speter    SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath,
181251881Speter                             TRUE /* copy_perms */,
182251881Speter                             scratch_pool));
183251881Speter  else
184251881Speter    SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool));
185251881Speter
186251881Speter  if (file_copy)
187251881Speter    {
188251881Speter      /* Remove 'read-only' from the destination file; it's a local add now. */
189251881Speter      SVN_ERR(svn_io_set_file_read_write(dst_tmp_abspath,
190251881Speter                                         FALSE, scratch_pool));
191251881Speter    }
192251881Speter
193251881Speter  SVN_ERR(svn_wc__wq_build_file_move(work_item, db, dst_abspath,
194251881Speter                                     dst_tmp_abspath, dst_abspath,
195251881Speter                                     result_pool, scratch_pool));
196251881Speter
197251881Speter  return SVN_NO_ERROR;
198251881Speter}
199251881Speter
200251881Speter/* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB.
201251881Speter   If METADATA_ONLY is true, copy only the versioned metadata,
202251881Speter   otherwise copy both the versioned metadata and the filesystem node (even
203251881Speter   if it is the wrong kind, and recursively if it is a dir).
204251881Speter
205251881Speter   If IS_MOVE is true, record move information in working copy meta
206251881Speter   data in addition to copying the file.
207251881Speter
208251881Speter   If the versioned file has a text conflict, and the .mine file exists in
209251881Speter   the filesystem, copy the .mine file to DST_ABSPATH.  Otherwise, copy the
210251881Speter   versioned file itself.
211251881Speter
212251881Speter   This also works for versioned symlinks that are stored in the db as
213289180Speter   svn_node_file with svn:special set.
214289180Speter
215289180Speter   If DIRENT is not NULL, it contains the on-disk information of SRC_ABSPATH.
216289180Speter   RECORDED_SIZE (if not SVN_INVALID_FILESIZE) contains the recorded size of
217289180Speter   SRC_ABSPATH, and RECORDED_TIME the recorded size or 0.
218289180Speter
219289180Speter   These values will be used to avoid unneeded work.
220289180Speter*/
221251881Speterstatic svn_error_t *
222251881Spetercopy_versioned_file(svn_wc__db_t *db,
223251881Speter                    const char *src_abspath,
224251881Speter                    const char *dst_abspath,
225251881Speter                    const char *dst_op_root_abspath,
226251881Speter                    const char *tmpdir_abspath,
227251881Speter                    svn_boolean_t metadata_only,
228251881Speter                    svn_boolean_t conflicted,
229251881Speter                    svn_boolean_t is_move,
230289180Speter                    const svn_io_dirent2_t *dirent,
231289180Speter                    svn_filesize_t recorded_size,
232289180Speter                    apr_time_t recorded_time,
233251881Speter                    svn_cancel_func_t cancel_func,
234251881Speter                    void *cancel_baton,
235251881Speter                    svn_wc_notify_func2_t notify_func,
236251881Speter                    void *notify_baton,
237251881Speter                    apr_pool_t *scratch_pool)
238251881Speter{
239251881Speter  svn_skel_t *work_items = NULL;
240251881Speter
241251881Speter  /* In case we are copying from one WC to another (e.g. an external dir),
242251881Speter     ensure the destination WC has a copy of the pristine text. */
243251881Speter
244251881Speter  /* Prepare a temp copy of the filesystem node.  It is usually a file, but
245251881Speter     copy recursively if it's a dir. */
246251881Speter  if (!metadata_only)
247251881Speter    {
248251881Speter      const char *my_src_abspath = NULL;
249251881Speter      svn_boolean_t handle_as_unversioned = FALSE;
250251881Speter
251251881Speter      /* By default, take the copy source as given. */
252251881Speter      my_src_abspath = src_abspath;
253251881Speter
254251881Speter      if (conflicted)
255251881Speter        {
256251881Speter          svn_skel_t *conflict;
257251881Speter          const char *conflict_working;
258251881Speter          svn_error_t *err;
259251881Speter
260251881Speter          /* Is there a text conflict at the source path? */
261289180Speter          SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL,
262289180Speter                                           db, src_abspath,
263289180Speter                                           scratch_pool, scratch_pool));
264251881Speter
265251881Speter          err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL,
266251881Speter                                                    db, src_abspath, conflict,
267251881Speter                                                    scratch_pool,
268251881Speter                                                    scratch_pool);
269251881Speter
270251881Speter          if (err && err->apr_err == SVN_ERR_WC_MISSING)
271251881Speter            {
272251881Speter              /* not text conflicted */
273251881Speter              svn_error_clear(err);
274251881Speter              conflict_working = NULL;
275251881Speter            }
276251881Speter          else
277251881Speter            SVN_ERR(err);
278251881Speter
279251881Speter          if (conflict_working)
280251881Speter            {
281251881Speter              svn_node_kind_t working_kind;
282251881Speter
283251881Speter              /* Does the ".mine" file exist? */
284251881Speter              SVN_ERR(svn_io_check_path(conflict_working, &working_kind,
285251881Speter                                        scratch_pool));
286251881Speter
287251881Speter              if (working_kind == svn_node_file)
288251881Speter                {
289251881Speter                   /* Don't perform unmodified/pristine optimization */
290251881Speter                  handle_as_unversioned = TRUE;
291251881Speter                  my_src_abspath = conflict_working;
292251881Speter                }
293251881Speter            }
294251881Speter        }
295251881Speter
296251881Speter      SVN_ERR(copy_to_tmpdir(&work_items, NULL, db, my_src_abspath,
297251881Speter                             dst_abspath, tmpdir_abspath,
298251881Speter                             TRUE /* file_copy */,
299251881Speter                             handle_as_unversioned /* unversioned */,
300289180Speter                             dirent, recorded_size, recorded_time,
301251881Speter                             cancel_func, cancel_baton,
302251881Speter                             scratch_pool, scratch_pool));
303251881Speter    }
304251881Speter
305251881Speter  /* Copy the (single) node's metadata, and move the new filesystem node
306251881Speter     into place. */
307251881Speter  SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
308251881Speter                             dst_op_root_abspath, is_move, work_items,
309251881Speter                             scratch_pool));
310251881Speter
311251881Speter  if (notify_func)
312251881Speter    {
313251881Speter      svn_wc_notify_t *notify
314251881Speter        = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
315251881Speter                               scratch_pool);
316251881Speter      notify->kind = svn_node_file;
317251881Speter
318251881Speter      (*notify_func)(notify_baton, notify, scratch_pool);
319251881Speter    }
320251881Speter  return SVN_NO_ERROR;
321251881Speter}
322251881Speter
323251881Speter/* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB,
324251881Speter   recursively.  If METADATA_ONLY is true, copy only the versioned metadata,
325251881Speter   otherwise copy both the versioned metadata and the filesystem nodes (even
326251881Speter   if they are the wrong kind, and including unversioned children).
327251881Speter   If IS_MOVE is true, record move information in working copy meta
328251881Speter   data in addition to copying the directory.
329251881Speter
330251881Speter   WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root)
331289180Speter
332289180Speter   If DIRENT is not NULL, it contains the on-disk information of SRC_ABSPATH.
333251881Speter */
334251881Speterstatic svn_error_t *
335251881Spetercopy_versioned_dir(svn_wc__db_t *db,
336251881Speter                   const char *src_abspath,
337251881Speter                   const char *dst_abspath,
338251881Speter                   const char *dst_op_root_abspath,
339251881Speter                   const char *tmpdir_abspath,
340251881Speter                   svn_boolean_t metadata_only,
341251881Speter                   svn_boolean_t is_move,
342289180Speter                   const svn_io_dirent2_t *dirent,
343251881Speter                   svn_cancel_func_t cancel_func,
344251881Speter                   void *cancel_baton,
345251881Speter                   svn_wc_notify_func2_t notify_func,
346251881Speter                   void *notify_baton,
347251881Speter                   apr_pool_t *scratch_pool)
348251881Speter{
349251881Speter  svn_skel_t *work_items = NULL;
350251881Speter  const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
351251881Speter  apr_hash_t *versioned_children;
352251881Speter  apr_hash_t *conflicted_children;
353251881Speter  apr_hash_t *disk_children;
354251881Speter  apr_hash_index_t *hi;
355251881Speter  svn_node_kind_t disk_kind;
356251881Speter  apr_pool_t *iterpool;
357251881Speter
358251881Speter  /* Prepare a temp copy of the single filesystem node (usually a dir). */
359251881Speter  if (!metadata_only)
360251881Speter    {
361251881Speter      SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind,
362251881Speter                             db, src_abspath, dst_abspath,
363251881Speter                             tmpdir_abspath,
364251881Speter                             FALSE /* file_copy */,
365251881Speter                             FALSE /* unversioned */,
366289180Speter                             dirent, SVN_INVALID_FILESIZE, 0,
367251881Speter                             cancel_func, cancel_baton,
368251881Speter                             scratch_pool, scratch_pool));
369251881Speter    }
370251881Speter
371251881Speter  /* Copy the (single) node's metadata, and move the new filesystem node
372251881Speter     into place. */
373251881Speter  SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
374251881Speter                             dst_op_root_abspath, is_move, work_items,
375251881Speter                             scratch_pool));
376251881Speter
377251881Speter  if (notify_func)
378251881Speter    {
379251881Speter      svn_wc_notify_t *notify
380251881Speter        = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
381251881Speter                               scratch_pool);
382251881Speter      notify->kind = svn_node_dir;
383251881Speter
384251881Speter      /* When we notify that we performed a copy, make sure we already did */
385251881Speter      if (work_items != NULL)
386251881Speter        SVN_ERR(svn_wc__wq_run(db, dir_abspath,
387251881Speter                               cancel_func, cancel_baton, scratch_pool));
388251881Speter
389251881Speter      (*notify_func)(notify_baton, notify, scratch_pool);
390251881Speter    }
391251881Speter
392251881Speter  if (!metadata_only && disk_kind == svn_node_dir)
393251881Speter    /* All filesystem children, versioned and unversioned.  We're only
394251881Speter       interested in their names, so we can pass TRUE as the only_check_type
395251881Speter       param. */
396251881Speter    SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE,
397251881Speter                                scratch_pool, scratch_pool));
398251881Speter  else
399251881Speter    disk_children = NULL;
400251881Speter
401251881Speter  /* Copy all the versioned children */
402251881Speter  iterpool = svn_pool_create(scratch_pool);
403251881Speter  SVN_ERR(svn_wc__db_read_children_info(&versioned_children,
404251881Speter                                        &conflicted_children,
405251881Speter                                        db, src_abspath,
406289180Speter                                        FALSE /* base_tree_only */,
407251881Speter                                        scratch_pool, iterpool));
408251881Speter  for (hi = apr_hash_first(scratch_pool, versioned_children);
409251881Speter       hi;
410251881Speter       hi = apr_hash_next(hi))
411251881Speter    {
412251881Speter      const char *child_name, *child_src_abspath, *child_dst_abspath;
413251881Speter      struct svn_wc__db_info_t *info;
414251881Speter
415251881Speter      svn_pool_clear(iterpool);
416251881Speter
417251881Speter      if (cancel_func)
418251881Speter        SVN_ERR(cancel_func(cancel_baton));
419251881Speter
420289180Speter      child_name = apr_hash_this_key(hi);
421289180Speter      info = apr_hash_this_val(hi);
422251881Speter      child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool);
423251881Speter      child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool);
424251881Speter
425251881Speter      if (info->op_root)
426251881Speter        SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db,
427251881Speter                                                  child_src_abspath,
428251881Speter                                                  child_dst_abspath,
429251881Speter                                                  is_move,
430251881Speter                                                  scratch_pool));
431251881Speter
432251881Speter      if (info->status == svn_wc__db_status_normal
433251881Speter          || info->status == svn_wc__db_status_added)
434251881Speter        {
435251881Speter          /* We have more work to do than just changing the DB */
436251881Speter          if (info->kind == svn_node_file)
437251881Speter            {
438251881Speter              /* We should skip this node if this child is a file external
439251881Speter                 (issues #3589, #4000) */
440251881Speter              if (!info->file_external)
441251881Speter                SVN_ERR(copy_versioned_file(db,
442251881Speter                                            child_src_abspath,
443251881Speter                                            child_dst_abspath,
444251881Speter                                            dst_op_root_abspath,
445251881Speter                                            tmpdir_abspath,
446251881Speter                                            metadata_only, info->conflicted,
447251881Speter                                            is_move,
448289180Speter                                            disk_children
449289180Speter                                              ? svn_hash_gets(disk_children,
450289180Speter                                                              child_name)
451289180Speter                                              : NULL,
452289180Speter                                            info->recorded_size,
453289180Speter                                            info->recorded_time,
454251881Speter                                            cancel_func, cancel_baton,
455251881Speter                                            NULL, NULL,
456251881Speter                                            iterpool));
457251881Speter            }
458251881Speter          else if (info->kind == svn_node_dir)
459251881Speter            SVN_ERR(copy_versioned_dir(db,
460251881Speter                                       child_src_abspath, child_dst_abspath,
461251881Speter                                       dst_op_root_abspath, tmpdir_abspath,
462251881Speter                                       metadata_only, is_move,
463289180Speter                                       disk_children
464289180Speter                                              ? svn_hash_gets(disk_children,
465289180Speter                                                              child_name)
466289180Speter                                              : NULL,
467251881Speter                                       cancel_func, cancel_baton, NULL, NULL,
468251881Speter                                       iterpool));
469251881Speter          else
470251881Speter            return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
471251881Speter                                     _("cannot handle node kind for '%s'"),
472251881Speter                                     svn_dirent_local_style(child_src_abspath,
473251881Speter                                                            scratch_pool));
474251881Speter        }
475251881Speter      else if (info->status == svn_wc__db_status_deleted
476251881Speter          || info->status == svn_wc__db_status_not_present
477251881Speter          || info->status == svn_wc__db_status_excluded)
478251881Speter        {
479251881Speter          /* This will be copied as some kind of deletion. Don't touch
480251881Speter             any actual files */
481251881Speter          SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath,
482251881Speter                                     child_dst_abspath, dst_op_root_abspath,
483251881Speter                                     is_move, NULL, iterpool));
484251881Speter
485289180Speter          /* Don't recurse on children when all we do is creating not-present
486251881Speter             children */
487251881Speter        }
488251881Speter      else if (info->status == svn_wc__db_status_incomplete)
489251881Speter        {
490251881Speter          /* Should go ahead and copy incomplete to incomplete? Try to
491251881Speter             copy as much as possible, or give up early? */
492251881Speter          return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
493251881Speter                                   _("Cannot handle status of '%s'"),
494251881Speter                                   svn_dirent_local_style(child_src_abspath,
495251881Speter                                                          iterpool));
496251881Speter        }
497251881Speter      else
498251881Speter        {
499251881Speter          SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded);
500251881Speter
501251881Speter          return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
502251881Speter                                   _("Cannot copy '%s' excluded by server"),
503251881Speter                                   svn_dirent_local_style(child_src_abspath,
504251881Speter                                                          iterpool));
505251881Speter        }
506251881Speter
507251881Speter      if (disk_children
508251881Speter          && (info->status == svn_wc__db_status_normal
509251881Speter              || info->status == svn_wc__db_status_added))
510251881Speter        {
511251881Speter          /* Remove versioned child as it has been handled */
512251881Speter          svn_hash_sets(disk_children, child_name, NULL);
513251881Speter        }
514251881Speter    }
515251881Speter
516251881Speter  /* Copy the remaining filesystem children, which are unversioned, skipping
517251881Speter     any conflict-marker files. */
518251881Speter  if (disk_children && apr_hash_count(disk_children))
519251881Speter    {
520251881Speter      apr_hash_t *marker_files;
521251881Speter
522251881Speter      SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db,
523251881Speter                                                   src_abspath, scratch_pool,
524251881Speter                                                   scratch_pool));
525251881Speter
526251881Speter      work_items = NULL;
527251881Speter
528251881Speter      for (hi = apr_hash_first(scratch_pool, disk_children); hi;
529251881Speter           hi = apr_hash_next(hi))
530251881Speter        {
531289180Speter          const char *name = apr_hash_this_key(hi);
532251881Speter          const char *unver_src_abspath, *unver_dst_abspath;
533251881Speter          svn_skel_t *work_item;
534251881Speter
535251881Speter          if (svn_wc_is_adm_dir(name, iterpool))
536251881Speter            continue;
537251881Speter
538251881Speter          if (cancel_func)
539251881Speter            SVN_ERR(cancel_func(cancel_baton));
540251881Speter
541251881Speter          svn_pool_clear(iterpool);
542251881Speter          unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool);
543251881Speter          unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool);
544251881Speter
545251881Speter          if (marker_files &&
546251881Speter              svn_hash_gets(marker_files, unver_src_abspath))
547251881Speter            continue;
548251881Speter
549251881Speter          SVN_ERR(copy_to_tmpdir(&work_item, NULL, db, unver_src_abspath,
550251881Speter                                 unver_dst_abspath, tmpdir_abspath,
551251881Speter                                 TRUE /* recursive */, TRUE /* unversioned */,
552289180Speter                                 NULL, SVN_INVALID_FILESIZE, 0,
553251881Speter                                 cancel_func, cancel_baton,
554251881Speter                                 scratch_pool, iterpool));
555251881Speter
556251881Speter          if (work_item)
557251881Speter            work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
558251881Speter        }
559251881Speter      SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool));
560251881Speter    }
561251881Speter
562251881Speter  svn_pool_destroy(iterpool);
563251881Speter
564251881Speter  return SVN_NO_ERROR;
565251881Speter}
566251881Speter
567251881Speter
568251881Speter/* The guts of svn_wc_copy3() and svn_wc_move().
569251881Speter * The additional parameter IS_MOVE indicates whether this is a copy or
570251881Speter * a move operation.
571251881Speter *
572289180Speter * If RECORD_MOVE_ON_DELETE is not NULL and a move had to be degraded
573289180Speter * to a copy, then set *RECORD_MOVE_ON_DELETE to FALSE. */
574251881Speterstatic svn_error_t *
575289180Spetercopy_or_move(svn_boolean_t *record_move_on_delete,
576251881Speter             svn_wc_context_t *wc_ctx,
577251881Speter             const char *src_abspath,
578251881Speter             const char *dst_abspath,
579251881Speter             svn_boolean_t metadata_only,
580251881Speter             svn_boolean_t is_move,
581251881Speter             svn_boolean_t allow_mixed_revisions,
582251881Speter             svn_cancel_func_t cancel_func,
583251881Speter             void *cancel_baton,
584251881Speter             svn_wc_notify_func2_t notify_func,
585251881Speter             void *notify_baton,
586251881Speter             apr_pool_t *scratch_pool)
587251881Speter{
588251881Speter  svn_wc__db_t *db = wc_ctx->db;
589251881Speter  svn_node_kind_t src_db_kind;
590251881Speter  const char *dstdir_abspath;
591251881Speter  svn_boolean_t conflicted;
592251881Speter  const char *tmpdir_abspath;
593251881Speter  const char *src_wcroot_abspath;
594251881Speter  const char *dst_wcroot_abspath;
595251881Speter  svn_boolean_t within_one_wc;
596251881Speter  svn_wc__db_status_t src_status;
597251881Speter  svn_error_t *err;
598289180Speter  svn_filesize_t recorded_size;
599289180Speter  apr_time_t recorded_time;
600251881Speter
601251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
602251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
603251881Speter
604251881Speter  dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
605251881Speter
606251881Speter  /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH;
607251881Speter     throw an error if not. */
608251881Speter  {
609251881Speter    svn_wc__db_status_t dstdir_status;
610251881Speter    const char *src_repos_root_url, *dst_repos_root_url;
611251881Speter    const char *src_repos_uuid, *dst_repos_uuid;
612251881Speter    const char *src_repos_relpath;
613251881Speter
614251881Speter    err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL,
615251881Speter                               &src_repos_relpath, &src_repos_root_url,
616251881Speter                               &src_repos_uuid, NULL, NULL, NULL, NULL, NULL,
617289180Speter                               NULL, NULL, NULL, NULL, NULL, NULL,
618289180Speter                               &recorded_size, &recorded_time,
619251881Speter                               NULL, &conflicted, NULL, NULL, NULL, NULL,
620251881Speter                               NULL, NULL,
621251881Speter                               db, src_abspath, scratch_pool, scratch_pool);
622251881Speter
623251881Speter    if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
624251881Speter      {
625251881Speter        /* Replicate old error code and text */
626251881Speter        svn_error_clear(err);
627251881Speter        return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
628251881Speter                                 _("'%s' is not under version control"),
629251881Speter                                 svn_dirent_local_style(src_abspath,
630251881Speter                                                        scratch_pool));
631251881Speter      }
632251881Speter    else
633251881Speter      SVN_ERR(err);
634251881Speter
635251881Speter    /* Do this now, as we know the right data is cached */
636251881Speter    SVN_ERR(svn_wc__db_get_wcroot(&src_wcroot_abspath, db, src_abspath,
637251881Speter                                  scratch_pool, scratch_pool));
638251881Speter
639251881Speter    switch (src_status)
640251881Speter      {
641251881Speter        case svn_wc__db_status_deleted:
642251881Speter          return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
643251881Speter                                   _("Deleted node '%s' can't be copied."),
644251881Speter                                   svn_dirent_local_style(src_abspath,
645251881Speter                                                          scratch_pool));
646251881Speter
647251881Speter        case svn_wc__db_status_excluded:
648251881Speter        case svn_wc__db_status_server_excluded:
649251881Speter        case svn_wc__db_status_not_present:
650251881Speter          return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
651251881Speter                                   _("The node '%s' was not found."),
652251881Speter                                   svn_dirent_local_style(src_abspath,
653251881Speter                                                          scratch_pool));
654251881Speter        default:
655251881Speter          break;
656251881Speter      }
657251881Speter
658251881Speter     if (is_move && ! strcmp(src_abspath, src_wcroot_abspath))
659251881Speter      {
660251881Speter        return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
661251881Speter                                 _("'%s' is the root of a working copy and "
662251881Speter                                   "cannot be moved"),
663251881Speter                                   svn_dirent_local_style(src_abspath,
664251881Speter                                                          scratch_pool));
665251881Speter      }
666251881Speter    if (is_move && src_repos_relpath && !src_repos_relpath[0])
667251881Speter      {
668251881Speter        return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
669251881Speter                                 _("'%s' represents the repository root "
670251881Speter                                   "and cannot be moved"),
671251881Speter                                 svn_dirent_local_style(src_abspath,
672251881Speter                                                        scratch_pool));
673251881Speter      }
674251881Speter
675251881Speter    err = svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL,
676251881Speter                               &dst_repos_root_url, &dst_repos_uuid, NULL,
677251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
678251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL,
679251881Speter                               NULL, NULL, NULL, NULL,
680251881Speter                               NULL, NULL, NULL,
681251881Speter                               db, dstdir_abspath,
682251881Speter                               scratch_pool, scratch_pool);
683251881Speter
684251881Speter    if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
685251881Speter      {
686251881Speter        /* An unversioned destination directory exists on disk. */
687251881Speter        svn_error_clear(err);
688251881Speter        return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
689251881Speter                                 _("'%s' is not under version control"),
690251881Speter                                 svn_dirent_local_style(dstdir_abspath,
691251881Speter                                                        scratch_pool));
692251881Speter      }
693251881Speter    else
694251881Speter      SVN_ERR(err);
695251881Speter
696251881Speter    /* Do this now, as we know the right data is cached */
697251881Speter    SVN_ERR(svn_wc__db_get_wcroot(&dst_wcroot_abspath, db, dstdir_abspath,
698251881Speter                                  scratch_pool, scratch_pool));
699251881Speter
700251881Speter    if (!src_repos_root_url)
701251881Speter      {
702251881Speter        if (src_status == svn_wc__db_status_added)
703251881Speter          SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
704251881Speter                                           &src_repos_root_url,
705251881Speter                                           &src_repos_uuid, NULL, NULL, NULL,
706251881Speter                                           NULL,
707251881Speter                                           db, src_abspath,
708251881Speter                                           scratch_pool, scratch_pool));
709251881Speter        else
710251881Speter          /* If not added, the node must have a base or we can't copy */
711289180Speter          SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL,
712289180Speter                                           &src_repos_root_url,
713289180Speter                                           &src_repos_uuid, NULL, NULL, NULL,
714289180Speter                                           NULL, NULL, NULL, NULL, NULL, NULL,
715289180Speter                                           NULL,
716289180Speter                                           db, src_abspath,
717289180Speter                                           scratch_pool, scratch_pool));
718251881Speter      }
719251881Speter
720251881Speter    if (!dst_repos_root_url)
721251881Speter      {
722251881Speter        if (dstdir_status == svn_wc__db_status_added)
723251881Speter          SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
724251881Speter                                           &dst_repos_root_url,
725251881Speter                                           &dst_repos_uuid, NULL, NULL, NULL,
726251881Speter                                           NULL,
727251881Speter                                           db, dstdir_abspath,
728251881Speter                                           scratch_pool, scratch_pool));
729251881Speter        else
730251881Speter          /* If not added, the node must have a base or we can't copy */
731289180Speter          SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL,
732289180Speter                                           &dst_repos_root_url,
733289180Speter                                           &dst_repos_uuid, NULL, NULL, NULL,
734289180Speter                                           NULL, NULL, NULL, NULL, NULL, NULL,
735289180Speter                                           NULL,
736289180Speter                                           db, dstdir_abspath,
737289180Speter                                           scratch_pool, scratch_pool));
738251881Speter      }
739251881Speter
740251881Speter    if (strcmp(src_repos_root_url, dst_repos_root_url) != 0
741251881Speter        || strcmp(src_repos_uuid, dst_repos_uuid) != 0)
742251881Speter      return svn_error_createf(
743251881Speter         SVN_ERR_WC_INVALID_SCHEDULE, NULL,
744251881Speter         _("Cannot copy to '%s', as it is not from repository '%s'; "
745251881Speter           "it is from '%s'"),
746251881Speter         svn_dirent_local_style(dst_abspath, scratch_pool),
747251881Speter         src_repos_root_url, dst_repos_root_url);
748251881Speter
749251881Speter    if (dstdir_status == svn_wc__db_status_deleted)
750251881Speter      return svn_error_createf(
751251881Speter         SVN_ERR_WC_INVALID_SCHEDULE, NULL,
752251881Speter         _("Cannot copy to '%s' as it is scheduled for deletion"),
753251881Speter         svn_dirent_local_style(dst_abspath, scratch_pool));
754251881Speter         /* ### should report dstdir_abspath instead of dst_abspath? */
755251881Speter  }
756251881Speter
757251881Speter  /* TODO(#2843): Rework the error report. */
758251881Speter  /* Check if the copy target is missing or hidden and thus not exist on the
759251881Speter     disk, before actually doing the file copy. */
760251881Speter  {
761251881Speter    svn_wc__db_status_t dst_status;
762251881Speter
763251881Speter    err = svn_wc__db_read_info(&dst_status, NULL, NULL, NULL, NULL, NULL,
764251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
765251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
766251881Speter                               NULL, NULL, NULL, NULL, NULL,
767251881Speter                               db, dst_abspath, scratch_pool, scratch_pool);
768251881Speter
769251881Speter    if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
770251881Speter      return svn_error_trace(err);
771251881Speter
772251881Speter    svn_error_clear(err);
773251881Speter
774251881Speter    if (!err)
775251881Speter      switch (dst_status)
776251881Speter        {
777251881Speter          case svn_wc__db_status_excluded:
778251881Speter            return svn_error_createf(
779251881Speter                     SVN_ERR_ENTRY_EXISTS, NULL,
780251881Speter                     _("'%s' is already under version control "
781251881Speter                       "but is excluded."),
782251881Speter                     svn_dirent_local_style(dst_abspath, scratch_pool));
783251881Speter          case svn_wc__db_status_server_excluded:
784251881Speter            return svn_error_createf(
785251881Speter                     SVN_ERR_ENTRY_EXISTS, NULL,
786251881Speter                     _("'%s' is already under version control"),
787251881Speter                     svn_dirent_local_style(dst_abspath, scratch_pool));
788251881Speter
789251881Speter          case svn_wc__db_status_deleted:
790251881Speter          case svn_wc__db_status_not_present:
791251881Speter            break; /* OK to add */
792251881Speter
793251881Speter          default:
794362181Sdim            if (!metadata_only)
795362181Sdim              return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
796362181Sdim                                 _("There is already a versioned item '%s'"),
797362181Sdim                                 svn_dirent_local_style(dst_abspath,
798362181Sdim                                                        scratch_pool));
799251881Speter        }
800251881Speter  }
801251881Speter
802251881Speter  /* Check that the target path is not obstructed, if required. */
803251881Speter  if (!metadata_only)
804251881Speter    {
805251881Speter      svn_node_kind_t dst_kind;
806251881Speter
807251881Speter      /* (We need only to check the root of the copy, not every path inside
808251881Speter         copy_versioned_file/_dir.) */
809251881Speter      SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind, scratch_pool));
810251881Speter      if (dst_kind != svn_node_none)
811251881Speter        return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
812251881Speter                                 _("'%s' already exists and is in the way"),
813251881Speter                                 svn_dirent_local_style(dst_abspath,
814251881Speter                                                        scratch_pool));
815251881Speter    }
816251881Speter
817251881Speter  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
818251881Speter                                         dstdir_abspath,
819251881Speter                                         scratch_pool, scratch_pool));
820251881Speter
821251881Speter  within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0);
822251881Speter
823251881Speter  if (is_move
824251881Speter      && !within_one_wc)
825251881Speter    {
826289180Speter      if (record_move_on_delete)
827289180Speter        *record_move_on_delete = FALSE;
828251881Speter
829251881Speter      is_move = FALSE;
830251881Speter    }
831251881Speter
832251881Speter  if (!within_one_wc)
833251881Speter    SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath,
834251881Speter                                         cancel_func, cancel_baton,
835251881Speter                                         scratch_pool));
836251881Speter
837251881Speter  if (src_db_kind == svn_node_file
838251881Speter      || src_db_kind == svn_node_symlink)
839251881Speter    {
840251881Speter      err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath,
841251881Speter                                tmpdir_abspath,
842251881Speter                                metadata_only, conflicted, is_move,
843289180Speter                                NULL, recorded_size, recorded_time,
844251881Speter                                cancel_func, cancel_baton,
845251881Speter                                notify_func, notify_baton,
846251881Speter                                scratch_pool);
847251881Speter    }
848251881Speter  else
849251881Speter    {
850251881Speter      if (is_move
851251881Speter          && src_status == svn_wc__db_status_normal)
852251881Speter        {
853251881Speter          svn_revnum_t min_rev;
854251881Speter          svn_revnum_t max_rev;
855251881Speter
856251881Speter          /* Verify that the move source is a single-revision subtree. */
857251881Speter          SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db,
858251881Speter                                               src_abspath, FALSE, scratch_pool));
859251881Speter          if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) &&
860251881Speter              min_rev != max_rev)
861251881Speter            {
862251881Speter              if (!allow_mixed_revisions)
863251881Speter                return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL,
864251881Speter                                         _("Cannot move mixed-revision "
865251881Speter                                           "subtree '%s' [%ld:%ld]; "
866251881Speter                                           "try updating it first"),
867251881Speter                                         svn_dirent_local_style(src_abspath,
868251881Speter                                                                scratch_pool),
869251881Speter                                         min_rev, max_rev);
870251881Speter
871289180Speter#ifndef RECORD_MIXED_MOVE
872251881Speter              is_move = FALSE;
873289180Speter              if (record_move_on_delete)
874289180Speter                *record_move_on_delete = FALSE;
875289180Speter#endif
876251881Speter            }
877251881Speter        }
878251881Speter
879251881Speter      err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath,
880251881Speter                               tmpdir_abspath, metadata_only, is_move,
881289180Speter                               NULL /* dirent */,
882251881Speter                               cancel_func, cancel_baton,
883251881Speter                               notify_func, notify_baton,
884251881Speter                               scratch_pool);
885251881Speter    }
886251881Speter
887251881Speter  if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED))
888251881Speter    return svn_error_trace(err);
889251881Speter
890251881Speter  if (is_move)
891251881Speter    err = svn_error_compose_create(err,
892251881Speter                svn_wc__db_op_handle_move_back(NULL,
893251881Speter                                               db, dst_abspath, src_abspath,
894251881Speter                                               NULL /* work_items */,
895251881Speter                                               scratch_pool));
896251881Speter
897251881Speter  /* Run the work queue with the remaining work */
898251881Speter  SVN_ERR(svn_error_compose_create(
899251881Speter                                err,
900251881Speter                                svn_wc__wq_run(db, dst_abspath,
901251881Speter                                                   cancel_func, cancel_baton,
902251881Speter                                                   scratch_pool)));
903251881Speter
904251881Speter  return SVN_NO_ERROR;
905251881Speter}
906251881Speter
907251881Speter
908251881Speter/* Public Interface */
909251881Speter
910251881Spetersvn_error_t *
911251881Spetersvn_wc_copy3(svn_wc_context_t *wc_ctx,
912251881Speter             const char *src_abspath,
913251881Speter             const char *dst_abspath,
914251881Speter             svn_boolean_t metadata_only,
915251881Speter             svn_cancel_func_t cancel_func,
916251881Speter             void *cancel_baton,
917251881Speter             svn_wc_notify_func2_t notify_func,
918251881Speter             void *notify_baton,
919251881Speter             apr_pool_t *scratch_pool)
920251881Speter{
921251881Speter  /* Verify that we have the required write lock. */
922251881Speter  SVN_ERR(svn_wc__write_check(wc_ctx->db,
923251881Speter                              svn_dirent_dirname(dst_abspath, scratch_pool),
924251881Speter                              scratch_pool));
925251881Speter
926251881Speter  return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath,
927251881Speter                                      metadata_only, FALSE /* is_move */,
928251881Speter                                      TRUE /* allow_mixed_revisions */,
929251881Speter                                      cancel_func, cancel_baton,
930251881Speter                                      notify_func, notify_baton,
931251881Speter                                      scratch_pool));
932251881Speter}
933251881Speter
934251881Speter
935251881Speter/* Remove the conflict markers of NODE_ABSPATH, that were left over after
936251881Speter   copying NODE_ABSPATH from SRC_ABSPATH.
937251881Speter
938251881Speter   Only use this function when you know what you're doing. This function
939251881Speter   explicitly ignores some case insensitivity issues!
940251881Speter
941251881Speter   */
942251881Speterstatic svn_error_t *
943251881Speterremove_node_conflict_markers(svn_wc__db_t *db,
944251881Speter                             const char *src_abspath,
945251881Speter                             const char *node_abspath,
946251881Speter                             apr_pool_t *scratch_pool)
947251881Speter{
948251881Speter  svn_skel_t *conflict;
949251881Speter
950289180Speter  SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL,
951289180Speter                                   db, src_abspath,
952251881Speter                                   scratch_pool, scratch_pool));
953251881Speter
954251881Speter  /* Do we have conflict markers that should be removed? */
955251881Speter  if (conflict != NULL)
956251881Speter    {
957251881Speter      const apr_array_header_t *markers;
958251881Speter      int i;
959251881Speter      const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool);
960251881Speter      const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool);
961251881Speter
962251881Speter      SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath,
963251881Speter                                            conflict,
964251881Speter                                            scratch_pool, scratch_pool));
965251881Speter
966251881Speter      /* No iterpool: Maximum number of possible conflict markers is 4 */
967251881Speter      for (i = 0; markers && (i < markers->nelts); i++)
968251881Speter        {
969251881Speter          const char *marker_abspath;
970251881Speter          const char *child_relpath;
971286506Speter          const char *child_abspath;
972251881Speter
973251881Speter          marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
974251881Speter
975286506Speter          child_relpath = svn_dirent_skip_ancestor(src_dir, marker_abspath);
976251881Speter
977251881Speter          if (child_relpath)
978251881Speter            {
979286506Speter              child_abspath = svn_dirent_join(dst_dir, child_relpath,
980286506Speter                                              scratch_pool);
981251881Speter
982286506Speter              SVN_ERR(svn_io_remove_file2(child_abspath, TRUE, scratch_pool));
983251881Speter            }
984251881Speter        }
985251881Speter    }
986251881Speter
987251881Speter  return SVN_NO_ERROR;
988251881Speter}
989251881Speter
990251881Speter/* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over
991251881Speter   after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH.
992251881Speter
993251881Speter   This function doesn't remove the conflict markers on WC_DIR_ABSPATH
994251881Speter   itself!
995251881Speter
996251881Speter   Only use this function when you know what you're doing. This function
997251881Speter   explicitly ignores some case insensitivity issues!
998251881Speter   */
999251881Speterstatic svn_error_t *
1000251881Speterremove_all_conflict_markers(svn_wc__db_t *db,
1001251881Speter                            const char *src_dir_abspath,
1002286506Speter                            const char *dst_dir_abspath,
1003289180Speter                            svn_cancel_func_t cancel_func,
1004289180Speter                            void *cancel_baton,
1005251881Speter                            apr_pool_t *scratch_pool)
1006251881Speter{
1007251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1008251881Speter  apr_hash_t *nodes;
1009251881Speter  apr_hash_t *conflicts; /* Unused */
1010251881Speter  apr_hash_index_t *hi;
1011251881Speter
1012251881Speter  /* Reuse a status helper to obtain all subdirs and conflicts in a single
1013251881Speter     db transaction. */
1014251881Speter  /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy
1015251881Speter          artillery. */
1016251881Speter  SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db,
1017251881Speter                                        src_dir_abspath,
1018289180Speter                                        FALSE /* base_tree_only */,
1019251881Speter                                        scratch_pool, iterpool));
1020251881Speter
1021251881Speter  for (hi = apr_hash_first(scratch_pool, nodes);
1022251881Speter       hi;
1023251881Speter       hi = apr_hash_next(hi))
1024251881Speter    {
1025289180Speter      const char *name = apr_hash_this_key(hi);
1026289180Speter      struct svn_wc__db_info_t *info = apr_hash_this_val(hi);
1027251881Speter
1028289180Speter      if (cancel_func)
1029289180Speter        SVN_ERR(cancel_func(cancel_baton));
1030289180Speter
1031251881Speter      if (info->conflicted)
1032251881Speter        {
1033251881Speter          svn_pool_clear(iterpool);
1034251881Speter          SVN_ERR(remove_node_conflict_markers(
1035251881Speter                            db,
1036251881Speter                            svn_dirent_join(src_dir_abspath, name, iterpool),
1037286506Speter                            svn_dirent_join(dst_dir_abspath, name, iterpool),
1038251881Speter                            iterpool));
1039251881Speter        }
1040251881Speter      if (info->kind == svn_node_dir)
1041251881Speter        {
1042251881Speter          svn_pool_clear(iterpool);
1043251881Speter          SVN_ERR(remove_all_conflict_markers(
1044251881Speter                            db,
1045251881Speter                            svn_dirent_join(src_dir_abspath, name, iterpool),
1046286506Speter                            svn_dirent_join(dst_dir_abspath, name, iterpool),
1047289180Speter                            cancel_func, cancel_baton,
1048251881Speter                            iterpool));
1049251881Speter        }
1050251881Speter    }
1051251881Speter
1052251881Speter  svn_pool_destroy(iterpool);
1053251881Speter  return SVN_NO_ERROR;
1054251881Speter}
1055251881Speter
1056251881Spetersvn_error_t *
1057251881Spetersvn_wc__move2(svn_wc_context_t *wc_ctx,
1058251881Speter              const char *src_abspath,
1059251881Speter              const char *dst_abspath,
1060251881Speter              svn_boolean_t metadata_only,
1061251881Speter              svn_boolean_t allow_mixed_revisions,
1062251881Speter              svn_cancel_func_t cancel_func,
1063251881Speter              void *cancel_baton,
1064251881Speter              svn_wc_notify_func2_t notify_func,
1065251881Speter              void *notify_baton,
1066251881Speter              apr_pool_t *scratch_pool)
1067251881Speter{
1068251881Speter  svn_wc__db_t *db = wc_ctx->db;
1069289180Speter  svn_boolean_t record_on_delete = TRUE;
1070251881Speter  svn_node_kind_t kind;
1071251881Speter  svn_boolean_t conflicted;
1072251881Speter
1073251881Speter  /* Verify that we have the required write locks. */
1074251881Speter  SVN_ERR(svn_wc__write_check(wc_ctx->db,
1075251881Speter                              svn_dirent_dirname(src_abspath, scratch_pool),
1076251881Speter                              scratch_pool));
1077251881Speter  SVN_ERR(svn_wc__write_check(wc_ctx->db,
1078251881Speter                              svn_dirent_dirname(dst_abspath, scratch_pool),
1079251881Speter                              scratch_pool));
1080251881Speter
1081289180Speter  SVN_ERR(copy_or_move(&record_on_delete,
1082251881Speter                       wc_ctx, src_abspath, dst_abspath,
1083251881Speter                       TRUE /* metadata_only */,
1084251881Speter                       TRUE /* is_move */,
1085251881Speter                       allow_mixed_revisions,
1086251881Speter                       cancel_func, cancel_baton,
1087251881Speter                       notify_func, notify_baton,
1088251881Speter                       scratch_pool));
1089251881Speter
1090251881Speter  /* An interrupt at this point will leave the new copy marked as
1091251881Speter     moved-here but the source has not yet been deleted or marked as
1092251881Speter     moved-to. */
1093251881Speter
1094251881Speter  /* Should we be using a workqueue for this move?  It's not clear.
1095251881Speter     What should happen if the copy above is interrupted?  The user
1096251881Speter     may want to abort the move and a workqueue might interfere with
1097251881Speter     that.
1098251881Speter
1099251881Speter     BH: On Windows it is not unlikely to encounter an access denied on
1100251881Speter     this line. Installing the move in the workqueue via the copy_or_move
1101251881Speter     might make it hard to recover from that situation, while the DB
1102251881Speter     is still in a valid state. So be careful when switching this over
1103251881Speter     to the workqueue. */
1104251881Speter  if (!metadata_only)
1105289180Speter    {
1106289180Speter      svn_error_t *err;
1107251881Speter
1108362181Sdim      err = svn_error_trace(svn_io_file_rename2(src_abspath, dst_abspath,
1109362181Sdim                                                FALSE, scratch_pool));
1110289180Speter
1111289180Speter      /* Let's try if we can keep wc.db consistent even when the move
1112289180Speter         fails. Deleting the target is a wc.db only operation, while
1113289180Speter         going forward (delaying the error) would try to change
1114289180Speter         conflict markers, which might also fail. */
1115289180Speter      if (err)
1116289180Speter        return svn_error_trace(
1117289180Speter          svn_error_compose_create(
1118289180Speter              err,
1119289180Speter              svn_wc__db_op_delete(wc_ctx->db, dst_abspath, NULL, TRUE,
1120289180Speter                                   NULL, NULL, cancel_func, cancel_baton,
1121289180Speter                                   NULL, NULL,
1122289180Speter                                   scratch_pool)));
1123289180Speter    }
1124289180Speter
1125251881Speter  SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL,
1126251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1127251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL,
1128251881Speter                               &conflicted, NULL, NULL, NULL,
1129251881Speter                               NULL, NULL, NULL,
1130251881Speter                               db, src_abspath,
1131251881Speter                               scratch_pool, scratch_pool));
1132251881Speter
1133251881Speter  if (kind == svn_node_dir)
1134251881Speter    SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath,
1135289180Speter                                        cancel_func, cancel_baton,
1136251881Speter                                        scratch_pool));
1137251881Speter
1138251881Speter  if (conflicted)
1139286506Speter    {
1140286506Speter      /* When we moved a directory, we moved the conflict markers
1141286506Speter         with the target... if we moved a file we only moved the
1142286506Speter         file itself and the markers are still in the old location */
1143286506Speter      SVN_ERR(remove_node_conflict_markers(db, src_abspath,
1144286506Speter                                           (kind == svn_node_dir)
1145286506Speter                                             ? dst_abspath
1146286506Speter                                             : src_abspath,
1147286506Speter                                           scratch_pool));
1148286506Speter    }
1149251881Speter
1150251881Speter  SVN_ERR(svn_wc__db_op_delete(db, src_abspath,
1151289180Speter                               record_on_delete ? dst_abspath : NULL,
1152251881Speter                               TRUE /* delete_dir_externals */,
1153251881Speter                               NULL /* conflict */, NULL /* work_items */,
1154251881Speter                               cancel_func, cancel_baton,
1155251881Speter                               notify_func, notify_baton,
1156251881Speter                               scratch_pool));
1157251881Speter
1158251881Speter  return SVN_NO_ERROR;
1159251881Speter}
1160