Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_wc / copy.c
blob819ac41e58a733517db6ec5f6872611866c5c91e
1 /*
2 * copy.c: wc 'copy' functionality.
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
23 /*** Includes. ***/
25 #include <string.h>
26 #include "svn_pools.h"
27 #include "svn_error.h"
28 #include "svn_path.h"
30 #include "wc.h"
31 #include "adm_files.h"
32 #include "entries.h"
33 #include "props.h"
34 #include "translate.h"
36 #include "svn_private_config.h"
37 #include "private/svn_wc_private.h"
40 /*** Code. ***/
42 /* Helper function for svn_wc_copy2() which handles WC->WC copying of
43 files which are scheduled for addition or unversioned.
45 Copy file SRC_PATH to DST_BASENAME in DST_PARENT_ACCESS.
47 DST_PARENT_ACCESS is a 0 depth locked access for a versioned directory
48 in the same WC as SRC_PATH.
50 If SRC_IS_ADDED is true then SRC_PATH is scheduled for addition and
51 DST_BASENAME will also be scheduled for addition.
53 If SRC_IS_ADDED is false then SRC_PATH is the unversioned child
54 file of a versioned or added parent and DST_BASENAME is simply copied.
56 Use POOL for all necessary allocations.
58 static svn_error_t *
59 copy_added_file_administratively(const char *src_path,
60 svn_boolean_t src_is_added,
61 svn_wc_adm_access_t *dst_parent_access,
62 const char *dst_basename,
63 svn_cancel_func_t cancel_func,
64 void *cancel_baton,
65 svn_wc_notify_func2_t notify_func,
66 void *notify_baton,
67 apr_pool_t *pool)
69 const char *dst_path
70 = svn_path_join(svn_wc_adm_access_path(dst_parent_access),
71 dst_basename, pool);
73 /* Copy this file and possibly put it under version control. */
74 SVN_ERR(svn_io_copy_file(src_path, dst_path, TRUE, pool));
76 if (src_is_added)
78 SVN_ERR(svn_wc_add2(dst_path, dst_parent_access, NULL,
79 SVN_INVALID_REVNUM, cancel_func,
80 cancel_baton, notify_func,
81 notify_baton, pool));
84 return SVN_NO_ERROR;
88 /* Helper function for svn_wc_copy2() which handles WC->WC copying of
89 directories which are scheduled for addition or unversioned.
91 Recursively copy directory SRC_PATH and its children, excluding
92 administrative directories, to DST_BASENAME in DST_PARENT_ACCESS.
94 DST_PARENT_ACCESS is a 0 depth locked access for a versioned directory
95 in the same WC as SRC_PATH.
97 SRC_ACCESS is a -1 depth access for SRC_PATH
99 If SRC_IS_ADDED is true then SRC_PATH is scheduled for addition and
100 DST_BASENAME will also be scheduled for addition.
102 If SRC_IS_ADDED is false then SRC_PATH is the unversioned child
103 directory of a versioned or added parent and DST_BASENAME is simply
104 copied.
106 Use POOL for all necessary allocations.
108 static svn_error_t *
109 copy_added_dir_administratively(const char *src_path,
110 svn_boolean_t src_is_added,
111 svn_wc_adm_access_t *dst_parent_access,
112 svn_wc_adm_access_t *src_access,
113 const char *dst_basename,
114 svn_cancel_func_t cancel_func,
115 void *cancel_baton,
116 svn_wc_notify_func2_t notify_func,
117 void *notify_baton,
118 apr_pool_t *pool)
120 const char *dst_parent = svn_wc_adm_access_path(dst_parent_access);
122 if (! src_is_added)
124 /* src_path is the top of an unversioned tree, just copy
125 the whole thing and we are done. */
126 SVN_ERR(svn_io_copy_dir_recursively(src_path, dst_parent, dst_basename,
127 TRUE, cancel_func, cancel_baton,
128 pool));
130 else
132 const svn_wc_entry_t *entry;
133 svn_wc_adm_access_t *dst_child_dir_access;
134 svn_wc_adm_access_t *src_child_dir_access;
135 apr_dir_t *dir;
136 apr_finfo_t this_entry;
137 svn_error_t *err;
138 apr_pool_t *subpool;
139 apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
140 /* The 'dst_path' is simply dst_parent/dst_basename */
141 const char *dst_path = svn_path_join(dst_parent, dst_basename, pool);
143 /* Check cancellation; note that this catches recursive calls too. */
144 if (cancel_func)
145 SVN_ERR(cancel_func(cancel_baton));
147 /* "Copy" the dir dst_path and schedule it, and possibly
148 its children, for addition. */
149 SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool));
151 /* Add the directory, adding locking access for dst_path
152 to dst_parent_access at the same time. */
153 SVN_ERR(svn_wc_add2(dst_path, dst_parent_access, NULL,
154 SVN_INVALID_REVNUM, cancel_func, cancel_baton,
155 notify_func, notify_baton, pool));
157 /* Get the accesses for the newly added dir and its source, we'll
158 need both to process any of SRC_PATHS's children below. */
159 SVN_ERR(svn_wc_adm_retrieve(&dst_child_dir_access, dst_parent_access,
160 dst_path, pool));
161 SVN_ERR(svn_wc_adm_retrieve(&src_child_dir_access, src_access,
162 src_path, pool));
164 SVN_ERR(svn_io_dir_open(&dir, src_path, pool));
166 subpool = svn_pool_create(pool);
168 /* Read src_path's entries one by one. */
169 while (1)
171 const char *src_fullpath;
173 svn_pool_clear(subpool);
175 err = svn_io_dir_read(&this_entry, flags, dir, subpool);
177 if (err)
179 /* Check if we're done reading the dir's entries. */
180 if (APR_STATUS_IS_ENOENT(err->apr_err))
182 apr_status_t apr_err;
184 svn_error_clear(err);
185 apr_err = apr_dir_close(dir);
186 if (apr_err)
187 return svn_error_wrap_apr(apr_err,
188 _("Can't close "
189 "directory '%s'"),
190 svn_path_local_style(src_path,
191 subpool));
192 break;
194 else
196 return svn_error_createf(err->apr_err, err,
197 _("Error during recursive copy "
198 "of '%s'"),
199 svn_path_local_style(src_path,
200 subpool));
204 /* Skip entries for this dir and its parent. */
205 if (this_entry.name[0] == '.'
206 && (this_entry.name[1] == '\0'
207 || (this_entry.name[1] == '.'
208 && this_entry.name[2] == '\0')))
209 continue;
211 /* Check cancellation so you can cancel during an
212 * add of a directory with lots of files. */
213 if (cancel_func)
214 SVN_ERR(cancel_func(cancel_baton));
216 /* Skip over SVN admin directories. */
217 if (svn_wc_is_adm_dir(this_entry.name, subpool))
218 continue;
220 /* Construct the full path of the entry. */
221 src_fullpath = svn_path_join(src_path, this_entry.name, subpool);
223 SVN_ERR(svn_wc_entry(&entry, src_fullpath, src_child_dir_access,
224 TRUE, subpool));
226 /* Recurse on directories; add files; ignore the rest. */
227 if (this_entry.filetype == APR_DIR)
229 SVN_ERR(copy_added_dir_administratively(src_fullpath,
230 entry ? TRUE : FALSE,
231 dst_child_dir_access,
232 src_child_dir_access,
233 this_entry.name,
234 cancel_func,
235 cancel_baton,
236 notify_func,
237 notify_baton,
238 subpool));
240 else if (this_entry.filetype != APR_UNKFILE)
242 SVN_ERR(copy_added_file_administratively(src_fullpath,
243 entry ? TRUE : FALSE,
244 dst_child_dir_access,
245 this_entry.name,
246 cancel_func,
247 cancel_baton,
248 notify_func,
249 notify_baton,
250 subpool));
253 } /* End while(1) loop */
255 svn_pool_destroy(subpool);
257 } /* End else src_is_added. */
259 return SVN_NO_ERROR;
263 /* Helper function for copy_file_administratively() and
264 copy_dir_administratively(). Determines the COPYFROM_URL and
265 COPYFROM_REV of a file or directory SRC_PATH which is the descendant
266 of an explicitly moved or copied directory that has not been committed.
268 static svn_error_t *
269 get_copyfrom_url_rev_via_parent(const char *src_path,
270 const char **copyfrom_url,
271 svn_revnum_t *copyfrom_rev,
272 svn_wc_adm_access_t *src_access,
273 apr_pool_t *pool)
275 const char *parent_path = svn_path_dirname(src_path, pool);
276 const char *rest = svn_path_basename(src_path, pool);
277 *copyfrom_url = NULL;
279 while (! *copyfrom_url)
281 svn_wc_adm_access_t *parent_access;
282 const svn_wc_entry_t *entry;
284 /* Don't look for parent_path in src_access if it can't be
285 there... */
286 if (svn_path_is_ancestor(svn_wc_adm_access_path(src_access),
287 parent_path))
289 SVN_ERR(svn_wc_adm_retrieve(&parent_access, src_access,
290 parent_path, pool));
291 SVN_ERR(svn_wc__entry_versioned(&entry, parent_path, parent_access,
292 FALSE, pool));
294 else /* ...get access for parent_path instead. */
296 SVN_ERR(svn_wc_adm_probe_open3(&parent_access, NULL,
297 parent_path, FALSE, -1,
298 NULL, NULL, pool));
299 SVN_ERR(svn_wc__entry_versioned(&entry, parent_path, parent_access,
300 FALSE, pool));
301 SVN_ERR(svn_wc_adm_close(parent_access));
304 if (entry->copyfrom_url)
306 *copyfrom_url = svn_path_join(entry->copyfrom_url, rest,
307 pool);
308 *copyfrom_rev = entry->copyfrom_rev;
310 else
312 rest = svn_path_join(svn_path_basename(parent_path, pool),
313 rest, pool);
314 parent_path = svn_path_dirname(parent_path, pool);
318 return SVN_NO_ERROR;
321 /* A helper for copy_file_administratively() which sets *COPYFROM_URL
322 and *COPYFROM_REV appropriately (possibly to NULL/SVN_INVALID_REVNUM).
323 DST_ENTRY may be NULL. */
324 static APR_INLINE svn_error_t *
325 determine_copyfrom_info(const char **copyfrom_url, svn_revnum_t *copyfrom_rev,
326 const char *src_path, svn_wc_adm_access_t *src_access,
327 const svn_wc_entry_t *src_entry,
328 const svn_wc_entry_t *dst_entry, apr_pool_t *pool)
330 const char *url;
331 svn_revnum_t rev;
333 if (src_entry->copyfrom_url)
335 /* When copying/moving a file that was already explicitly
336 copied/moved then we know the URL it was copied from... */
337 url = src_entry->copyfrom_url;
338 rev = src_entry->copyfrom_rev;
340 else
342 /* ...But if this file is merely the descendant of an explicitly
343 copied/moved directory, we need to do a bit more work to
344 determine copyfrom_url and copyfrom_rev. */
345 SVN_ERR(get_copyfrom_url_rev_via_parent(src_path, &url, &rev,
346 src_access, pool));
349 if (dst_entry && rev == dst_entry->revision &&
350 strcmp(url, dst_entry->url) == 0)
352 /* Suppress copyfrom info when the copy source is the same as
353 for the destination. */
354 url = NULL;
355 rev = SVN_INVALID_REVNUM;
357 else if (src_entry->copyfrom_url)
359 /* As the URL was allocated for src_entry, make a copy. */
360 url = apr_pstrdup(pool, url);
363 *copyfrom_url = url;
364 *copyfrom_rev = rev;
365 return SVN_NO_ERROR;
368 /* This function effectively creates and schedules a file for
369 addition, but does extra administrative things to allow it to
370 function as a 'copy'.
372 ASSUMPTIONS:
374 - src_path points to a file under version control
375 - dst_parent points to a dir under version control, in the same
376 working copy.
377 - dst_basename will be the 'new' name of the copied file in dst_parent
379 static svn_error_t *
380 copy_file_administratively(const char *src_path,
381 svn_wc_adm_access_t *src_access,
382 svn_wc_adm_access_t *dst_parent,
383 const char *dst_basename,
384 svn_wc_notify_func2_t notify_copied,
385 void *notify_baton,
386 apr_pool_t *pool)
388 svn_node_kind_t dst_kind;
389 const svn_wc_entry_t *src_entry, *dst_entry;
391 /* The 'dst_path' is simply dst_parent/dst_basename */
392 const char *dst_path
393 = svn_path_join(svn_wc_adm_access_path(dst_parent), dst_basename, pool);
395 /* Discover the paths to the two text-base files */
396 const char *src_txtb = svn_wc__text_base_path(src_path, FALSE, pool);
397 const char *tmp_txtb = svn_wc__text_base_path(dst_path, TRUE, pool);
399 /* Sanity check: if dst file exists already, don't allow overwrite. */
400 SVN_ERR(svn_io_check_path(dst_path, &dst_kind, pool));
401 if (dst_kind != svn_node_none)
402 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
403 _("'%s' already exists and is in the way"),
404 svn_path_local_style(dst_path, pool));
406 /* Even if DST_PATH doesn't exist it may still be a versioned file; it
407 may be scheduled for deletion, or the user may simply have removed the
408 working copy. Since we are going to write to DST_PATH text-base and
409 prop-base we need to detect such cases and abort. */
410 SVN_ERR(svn_wc_entry(&dst_entry, dst_path, dst_parent, FALSE, pool));
411 if (dst_entry && dst_entry->kind == svn_node_file)
413 if (dst_entry->schedule != svn_wc_schedule_delete)
414 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
415 _("There is already a versioned item '%s'"),
416 svn_path_local_style(dst_path, pool));
419 /* Sanity check 1: You cannot make a copy of something that's not
420 under version control. */
421 SVN_ERR(svn_wc__entry_versioned(&src_entry, src_path, src_access, FALSE,
422 pool));
424 /* Sanity check 2: You cannot make a copy of something that's not
425 in the repository unless it's a copy of an uncommitted copy. */
426 if ((src_entry->schedule == svn_wc_schedule_add && (! src_entry->copied))
427 || (! src_entry->url))
428 return svn_error_createf
429 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
430 _("Cannot copy or move '%s': it is not in the repository yet; "
431 "try committing first"),
432 svn_path_local_style(src_path, pool));
435 /* Schedule the new file for addition in its parent, WITH HISTORY. */
437 const char *copyfrom_url;
438 const char *tmp_wc_text;
439 svn_revnum_t copyfrom_rev;
440 apr_hash_t *props, *base_props;
442 /* Are we moving or copying a file that is already moved or copied
443 but not committed? */
444 if (src_entry->copied)
446 SVN_ERR(determine_copyfrom_info(&copyfrom_url, &copyfrom_rev, src_path,
447 src_access, src_entry, dst_entry,
448 pool));
450 else
452 /* Grrr. Why isn't the first arg to svn_wc_get_ancestry const? */
453 char *tmp;
455 SVN_ERR(svn_wc_get_ancestry(&tmp, &copyfrom_rev, src_path, src_access,
456 pool));
458 copyfrom_url = tmp;
461 /* Load source base and working props. */
462 SVN_ERR(svn_wc__load_props(&base_props, &props, NULL, src_access,
463 src_path, pool));
465 /* Copy pristine text-base to temporary location. */
466 SVN_ERR(svn_io_copy_file(src_txtb, tmp_txtb, TRUE, pool));
468 /* Copy working copy file to temporary location */
470 svn_boolean_t special;
472 SVN_ERR(svn_wc_create_tmp_file2(NULL, &tmp_wc_text,
473 svn_wc_adm_access_path(dst_parent),
474 svn_io_file_del_none, pool));
476 SVN_ERR(svn_wc__get_special(&special, src_path, src_access, pool));
477 if (special)
479 SVN_ERR(svn_subst_copy_and_translate3(src_path, tmp_wc_text,
480 NULL, FALSE, NULL,
481 FALSE, special, pool));
483 else
484 SVN_ERR(svn_io_copy_file(src_path, tmp_wc_text, TRUE, pool));
487 SVN_ERR(svn_wc_add_repos_file2(dst_path, dst_parent,
488 tmp_txtb, tmp_wc_text,
489 base_props, props,
490 copyfrom_url, copyfrom_rev, pool));
493 /* Report the addition to the caller. */
494 if (notify_copied != NULL)
496 svn_wc_notify_t *notify = svn_wc_create_notify(dst_path,
497 svn_wc_notify_add,
498 pool);
499 notify->kind = svn_node_file;
500 (*notify_copied)(notify_baton, notify, pool);
503 return SVN_NO_ERROR;
507 /* Recursively crawl over a directory PATH and do a number of things:
508 - Remove lock tokens
509 - Remove WC props
510 - Convert deleted items to schedule-delete items
511 - Set .svn directories to be hidden
513 static svn_error_t *
514 post_copy_cleanup(svn_wc_adm_access_t *adm_access,
515 apr_pool_t *pool)
517 apr_pool_t *subpool = svn_pool_create(pool);
518 apr_hash_t *entries;
519 apr_hash_index_t *hi;
520 svn_wc_entry_t *entry;
521 const char *path = svn_wc_adm_access_path(adm_access);
523 /* Remove wcprops. */
524 SVN_ERR(svn_wc__props_delete(path, svn_wc__props_wcprop, adm_access, pool));
526 /* Read this directory's entries file. */
527 SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
529 /* Because svn_io_copy_dir_recursively() doesn't copy directory
530 permissions, we'll patch up our tree's .svn subdirs to be
531 hidden. */
532 #ifdef APR_FILE_ATTR_HIDDEN
534 const char *adm_dir = svn_wc__adm_path(path, FALSE, pool, NULL);
535 const char *path_apr;
536 apr_status_t status;
537 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, adm_dir, pool));
538 status = apr_file_attrs_set(path_apr,
539 APR_FILE_ATTR_HIDDEN,
540 APR_FILE_ATTR_HIDDEN,
541 pool);
542 if (status)
543 return svn_error_wrap_apr(status, _("Can't hide directory '%s'"),
544 svn_path_local_style(adm_dir, pool));
546 #endif
548 /* Loop over all children, removing lock tokens and recursing into
549 directories. */
550 SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
551 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
553 const void *key;
554 void *val;
555 svn_node_kind_t kind;
556 svn_boolean_t deleted = FALSE;
557 apr_uint64_t flags = SVN_WC__ENTRY_MODIFY_FORCE;
559 svn_pool_clear(subpool);
561 apr_hash_this(hi, &key, NULL, &val);
562 entry = val;
563 kind = entry->kind;
564 deleted = entry->deleted;
566 /* Convert deleted="true" into schedule="delete" for all
567 children (and grandchildren, if RECURSE is set) of the path
568 represented by ADM_ACCESS. The result of this is that when
569 the copy is committed the items in question get deleted and
570 the result is a directory in the repository that matches the
571 original source directory for copy. If this were not done
572 the deleted="true" items would simply vanish from the entries
573 file as the copy is added to the working copy. The new
574 schedule="delete" files do not have a text-base and so their
575 scheduled deletion cannot be reverted. For directories a
576 placeholder with an svn_node_kind_t of svn_node_file and
577 schedule="delete" is used to avoid the problems associated
578 with creating a directory. See Issue #2101 for details. */
579 if (entry->deleted)
581 entry->schedule = svn_wc_schedule_delete;
582 flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE;
584 entry->deleted = FALSE;
585 flags |= SVN_WC__ENTRY_MODIFY_DELETED;
587 if (entry->kind == svn_node_dir)
589 /* ### WARNING: Very dodgy stuff here! ###
591 Directories are a problem since a schedule delete directory
592 needs an admin directory to be present. It's possible to
593 create a dummy admin directory and that sort of works, it's
594 good enough if the user commits the copy. Where it falls
595 down is if the user *reverts* the dummy directory since the
596 now schedule normal, copied, directory doesn't have the
597 correct contents.
599 The dodgy solution is to cheat and use a schedule delete file
600 as a placeholder! This is sufficient to provide a delete
601 when the copy is committed. Attempts to revert any such
602 "fake" files will fail due to a missing text-base. This
603 effectively means that the schedule deletes have to remain
604 schedule delete until the copy is committed, when they become
605 state deleted and everything works! */
606 entry->kind = svn_node_file;
607 flags |= SVN_WC__ENTRY_MODIFY_KIND;
611 /* Remove lock stuffs. */
612 if (entry->lock_token)
614 entry->lock_token = NULL;
615 entry->lock_owner = NULL;
616 entry->lock_comment = NULL;
617 entry->lock_creation_date = 0;
618 flags |= (SVN_WC__ENTRY_MODIFY_LOCK_TOKEN
619 | SVN_WC__ENTRY_MODIFY_LOCK_OWNER
620 | SVN_WC__ENTRY_MODIFY_LOCK_COMMENT
621 | SVN_WC__ENTRY_MODIFY_LOCK_CREATION_DATE);
624 /* If we meaningfully modified the flags, we must be wanting to
625 change the entry. */
626 if (flags != SVN_WC__ENTRY_MODIFY_FORCE)
627 SVN_ERR(svn_wc__entry_modify(adm_access, key, entry,
628 flags, TRUE, subpool));
630 /* If a dir, not deleted, and not "this dir", recurse. */
631 if ((! deleted)
632 && (kind == svn_node_dir)
633 && (strcmp(key, SVN_WC_ENTRY_THIS_DIR) != 0))
635 svn_wc_adm_access_t *child_access;
636 const char *child_path;
637 child_path = svn_path_join
638 (svn_wc_adm_access_path(adm_access), key, subpool);
639 SVN_ERR(svn_wc_adm_retrieve(&child_access, adm_access,
640 child_path, subpool));
641 SVN_ERR(post_copy_cleanup(child_access, subpool));
645 /* Cleanup */
646 svn_pool_destroy(subpool);
648 return SVN_NO_ERROR;
652 /* This function effectively creates and schedules a dir for
653 addition, but does extra administrative things to allow it to
654 function as a 'copy'.
656 ASSUMPTIONS:
658 - src_path points to a dir under version control
659 - dst_parent points to a dir under version control, in the same
660 working copy.
661 - dst_basename will be the 'new' name of the copied dir in dst_parent
663 static svn_error_t *
664 copy_dir_administratively(const char *src_path,
665 svn_wc_adm_access_t *src_access,
666 svn_wc_adm_access_t *dst_parent,
667 const char *dst_basename,
668 svn_cancel_func_t cancel_func,
669 void *cancel_baton,
670 svn_wc_notify_func2_t notify_copied,
671 void *notify_baton,
672 apr_pool_t *pool)
674 const svn_wc_entry_t *src_entry;
675 svn_wc_adm_access_t *adm_access;
677 /* The 'dst_path' is simply dst_parent/dst_basename */
678 const char *dst_path = svn_path_join(svn_wc_adm_access_path(dst_parent),
679 dst_basename, pool);
681 /* Sanity check 1: You cannot make a copy of something that's not
682 under version control. */
683 SVN_ERR(svn_wc__entry_versioned(&src_entry, src_path, src_access, FALSE,
684 pool));
686 /* Sanity check 2: You cannot make a copy of something that's not
687 in the repository unless it's a copy of an uncommitted copy. */
688 if ((src_entry->schedule == svn_wc_schedule_add && (! src_entry->copied))
689 || (! src_entry->url))
690 return svn_error_createf
691 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
692 _("Cannot copy or move '%s': it is not in the repository yet; "
693 "try committing first"),
694 svn_path_local_style(src_path, pool));
696 /* Recursively copy the whole directory over. This gets us all
697 text-base, props, base-props, as well as entries, local mods,
698 schedulings, existences, etc.
700 ### Should we be copying unversioned items within the directory? */
701 SVN_ERR(svn_io_copy_dir_recursively(src_path,
702 svn_wc_adm_access_path(dst_parent),
703 dst_basename,
704 TRUE,
705 cancel_func, cancel_baton,
706 pool));
708 /* If this is part of a move, the copied directory will be locked,
709 because the source directory was locked. Running cleanup will remove
710 the locks, even though this directory has not yet been added to the
711 parent. */
712 SVN_ERR(svn_wc_cleanup2(dst_path, NULL, cancel_func, cancel_baton, pool));
714 /* We've got some post-copy cleanup to do now. */
715 SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, dst_path, TRUE, -1,
716 cancel_func, cancel_baton, pool));
717 SVN_ERR(post_copy_cleanup(adm_access, pool));
719 /* Schedule the directory for addition in both its parent and itself
720 (this_dir) -- WITH HISTORY. This function should leave the
721 existing administrative dir untouched. */
723 const char *copyfrom_url;
724 svn_revnum_t copyfrom_rev;
725 svn_wc_entry_t tmp_entry;
727 /* Are we copying a dir that is already copied but not committed? */
728 if (src_entry->copied)
730 const svn_wc_entry_t *dst_entry;
731 SVN_ERR(svn_wc_entry(&dst_entry, dst_path, dst_parent, FALSE, pool));
732 SVN_ERR(determine_copyfrom_info(&copyfrom_url, &copyfrom_rev, src_path,
733 src_access, src_entry, dst_entry,
734 pool));
736 /* The URL for a copied dir won't exist in the repository, which
737 will cause svn_wc_add2() below to fail. Set the URL to the
738 URL of the first copy for now to prevent this. */
739 tmp_entry.url = apr_pstrdup(pool, copyfrom_url);
740 SVN_ERR(svn_wc__entry_modify(adm_access, NULL, /* This Dir */
741 &tmp_entry,
742 SVN_WC__ENTRY_MODIFY_URL, TRUE,
743 pool));
745 else
747 /* Grrr. Why isn't the first arg to svn_wc_get_ancestry const? */
748 char *tmp;
750 SVN_ERR(svn_wc_get_ancestry(&tmp, &copyfrom_rev, src_path, src_access,
751 pool));
753 copyfrom_url = tmp;
756 SVN_ERR(svn_wc_adm_close(adm_access));
758 SVN_ERR(svn_wc_add2(dst_path, dst_parent,
759 copyfrom_url, copyfrom_rev,
760 cancel_func, cancel_baton,
761 notify_copied, notify_baton, pool));
764 return SVN_NO_ERROR;
769 /* Public Interface */
771 svn_error_t *
772 svn_wc_copy2(const char *src_path,
773 svn_wc_adm_access_t *dst_parent,
774 const char *dst_basename,
775 svn_cancel_func_t cancel_func,
776 void *cancel_baton,
777 svn_wc_notify_func2_t notify_func,
778 void *notify_baton,
779 apr_pool_t *pool)
781 svn_wc_adm_access_t *adm_access;
782 svn_node_kind_t src_kind;
783 const char *dst_path;
784 const svn_wc_entry_t *dst_entry, *src_entry;
786 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, src_path, FALSE, -1,
787 cancel_func, cancel_baton, pool));
789 dst_path = svn_wc_adm_access_path(dst_parent);
790 SVN_ERR(svn_wc__entry_versioned(&dst_entry, dst_path, dst_parent, FALSE,
791 pool));
792 SVN_ERR(svn_wc__entry_versioned(&src_entry, src_path, adm_access, FALSE,
793 pool));
795 if ((src_entry->repos != NULL && dst_entry->repos != NULL) &&
796 strcmp(src_entry->repos, dst_entry->repos) != 0)
797 return svn_error_createf
798 (SVN_ERR_WC_INVALID_SCHEDULE, NULL,
799 _("Cannot copy to '%s', as it is not from repository '%s'; "
800 "it is from '%s'"),
801 svn_path_local_style(svn_wc_adm_access_path(dst_parent), pool),
802 src_entry->repos, dst_entry->repos);
803 if (dst_entry->schedule == svn_wc_schedule_delete)
804 return svn_error_createf
805 (SVN_ERR_WC_INVALID_SCHEDULE, NULL,
806 _("Cannot copy to '%s' as it is scheduled for deletion"),
807 svn_path_local_style(svn_wc_adm_access_path(dst_parent), pool));
809 SVN_ERR(svn_io_check_path(src_path, &src_kind, pool));
811 if (src_kind == svn_node_file)
813 /* Check if we are copying a file scheduled for addition,
814 these require special handling. */
815 if (src_entry->schedule == svn_wc_schedule_add
816 && (! src_entry->copied))
818 SVN_ERR(copy_added_file_administratively(src_path, TRUE,
819 dst_parent, dst_basename,
820 cancel_func, cancel_baton,
821 notify_func, notify_baton,
822 pool));
824 else
826 SVN_ERR(copy_file_administratively(src_path, adm_access,
827 dst_parent, dst_basename,
828 notify_func, notify_baton, pool));
831 else if (src_kind == svn_node_dir)
833 /* Check if we are copying a directory scheduled for addition,
834 these require special handling. */
835 if (src_entry->schedule == svn_wc_schedule_add
836 && (! src_entry->copied))
838 SVN_ERR(copy_added_dir_administratively(src_path, TRUE,
839 dst_parent, adm_access,
840 dst_basename, cancel_func,
841 cancel_baton, notify_func,
842 notify_baton, pool));
844 else
846 SVN_ERR(copy_dir_administratively(src_path, adm_access,
847 dst_parent, dst_basename,
848 cancel_func, cancel_baton,
849 notify_func, notify_baton, pool));
853 SVN_ERR(svn_wc_adm_close(adm_access));
856 return SVN_NO_ERROR;
860 svn_error_t *
861 svn_wc_copy(const char *src_path,
862 svn_wc_adm_access_t *dst_parent,
863 const char *dst_basename,
864 svn_cancel_func_t cancel_func,
865 void *cancel_baton,
866 svn_wc_notify_func_t notify_func,
867 void *notify_baton,
868 apr_pool_t *pool)
870 svn_wc__compat_notify_baton_t nb;
872 nb.func = notify_func;
873 nb.baton = notify_baton;
875 return svn_wc_copy2(src_path, dst_parent, dst_basename, cancel_func,
876 cancel_baton, svn_wc__compat_call_notify_func,
877 &nb, pool);