blob: 0c80d80a6e17740d75defe64e540b41f23c7c106 [file] [log] [blame]
/*
* copy.c: copy/move wrappers around wc 'copy' functionality.
*
* ====================================================================
* Copyright (c) 2000-2002 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
/* ==================================================================== */
/*** Includes. ***/
#include <string.h>
#include <assert.h>
#include "svn_wc.h"
#include "svn_client.h"
#include "svn_string.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_path.h"
#include "client.h"
/*** Code. ***/
/*
* if (not exist src_path)
* return ERR_BAD_SRC error
*
* if (exist dst_path)
* {
* if (dst_path is directory)
* copy src_path into dst_path as basename (src_path)
* else
* return ERR_OBSTRUCTION error
* }
* else
* copy src_path into parent_of_dst_path as basename (dst_path)
*
* if (this is a move)
* delete src_path
*/
/* Copy SRC_PATH into DST_PATH as DST_BASENAME, deleting SRC_PATH
afterwards if IS_MOVE is TRUE. Use POOL for all necessary
allocations.
### 838 OPTIONAL_ADM_ACCESS is an access baton with a write lock for the
### parent of DST_PATH. This parameter should be removed when issue 838
### stops using svn_client_copy.
*/
static svn_error_t *
wc_to_wc_copy (const char *src_path,
const char *dst_path,
svn_wc_adm_access_t *optional_adm_access,
svn_boolean_t is_move,
svn_boolean_t force,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
svn_node_kind_t src_kind, dst_kind;
const char *dst_parent, *base_name;
svn_wc_adm_access_t *adm_access, *src_access;
/* Verify that SRC_PATH exists. */
SVN_ERR (svn_io_check_path (src_path, &src_kind, pool));
if (src_kind == svn_node_none)
return svn_error_createf (SVN_ERR_NODE_UNKNOWN_KIND, 0, NULL, pool,
"path `%s' does not exist.", src_path);
/* If DST_PATH does not exist, then its basename will become a new
file or dir added to its parent (possibly an implicit '.'). If
DST_PATH is a dir, then SRC_PATH's basename will become a new
file or dir within DST_PATH itself. Else if it's a file, just
error out. */
SVN_ERR (svn_io_check_path (dst_path, &dst_kind, pool));
if (dst_kind == svn_node_none)
{
svn_path_split_nts (dst_path, &dst_parent, &base_name, pool);
}
else if (dst_kind == svn_node_dir)
{
svn_path_split_nts (src_path, NULL, &base_name, pool);
dst_parent = dst_path;
}
else
return svn_error_createf (SVN_ERR_ENTRY_EXISTS, 0, NULL, pool,
"file `%s' already exists.", dst_path);
if (is_move)
{
/* We don't handle optional_adm_access here. The merge code that
sets it calls svn_client_copy rather than svn_client_move */
const char *src_parent;
assert (! optional_adm_access);
svn_path_split_nts (src_path, &src_parent, NULL, pool);
SVN_ERR (svn_wc_adm_open (&src_access, NULL, src_parent, TRUE,
src_kind == svn_node_dir,
pool));
/* Need to avoid attempting to open the same dir twice when source
and destination overlap. */
if (strcmp (src_parent, dst_parent) == 0)
adm_access = src_access;
else
SVN_ERR (svn_wc_adm_open (&adm_access, NULL, dst_parent, TRUE, FALSE,
pool));
if (!force)
/* Ensure there are no "awkward" files. */
SVN_ERR_W (svn_client__can_delete (src_path, src_access, pool),
"Pass --force to override this restriction");
}
else if (! optional_adm_access)
SVN_ERR (svn_wc_adm_open (&adm_access, NULL, dst_parent, TRUE, FALSE,
pool));
else
adm_access = optional_adm_access;
/* Perform the copy and (optionally) delete. */
/* ### If this is not a move, we won't have locked the source, so we
### won't detect any outstanding locks. If the source is locked and
### requires cleanup should we abort the copy? */
SVN_ERR (svn_wc_copy (src_path, adm_access, base_name,
notify_func, notify_baton, pool));
if (is_move)
{
SVN_ERR (svn_wc_delete (src_path, src_access,
notify_func, notify_baton, pool));
if (adm_access != src_access)
SVN_ERR (svn_wc_adm_close (adm_access));
SVN_ERR (svn_wc_adm_close (src_access));
}
else if (! optional_adm_access)
SVN_ERR (svn_wc_adm_close (adm_access));
return SVN_NO_ERROR;
}
static svn_error_t *
repos_to_repos_copy (svn_client_commit_info_t **commit_info,
const char *src_url,
const svn_client_revision_t *src_revision,
const char *dst_url,
svn_client_auth_baton_t *auth_baton,
const char *message,
svn_boolean_t is_move,
apr_pool_t *pool)
{
const char *top_url, *src_rel, *dst_rel, *base_name, *unused;
apr_array_header_t *src_pieces = NULL, *dst_pieces = NULL;
svn_revnum_t youngest;
void *ra_baton, *sess;
svn_ra_plugin_t *ra_lib;
svn_node_kind_t src_kind, dst_kind;
const svn_delta_editor_t *editor;
void *edit_baton;
void *root_baton, *baton;
void **batons;
int i = 0;
svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
const char *committed_date = NULL;
const char *committed_author = NULL;
svn_revnum_t src_revnum;
const char *piece, *telepath;
/* ### TODO: Currently, this function will violate the depth-first
rule of editors when doing a move of something up into one of its
grandparent directories, such as:
svn mv http://server/repos/dir1/dir2/file http://server/repos/dir1
While it seems to work just fine, we might want to evaluate this
from a purely "correctness" standpoint.
*/
/* We have to open our session to the longest path common to both
SRC_URL and DST_URL in the repository so we can do existence
checks on both paths, and so we can operate on both paths in the
case of a move. */
top_url = svn_path_get_longest_ancestor (src_url, dst_url, pool);
/* Special edge-case! (issue #683) If you're resurrecting a
deleted item like this: 'svn cp -rN src_URL dst_URL', then it's
possible for src_URL == dst_URL == top_url. In this situation,
we want to open an RA session to the *parent* of all three. */
if (! strcmp (src_url, dst_url))
top_url = svn_path_remove_component_nts (top_url, pool);
/* Get the portions of the SRC and DST URLs that are relative to
TOP_URL. */
src_rel = svn_path_is_child (top_url, src_url, pool);
if (src_rel)
{
src_rel = svn_path_uri_decode (src_rel, pool);
src_pieces = svn_path_decompose (src_rel, pool);
if ((! src_pieces) || (! src_pieces->nelts))
return svn_error_createf
(SVN_ERR_WC_PATH_NOT_FOUND, 0, NULL, pool,
"error decomposing relative path `%s'", src_rel);
}
dst_rel = svn_path_is_child (top_url, dst_url, pool);
if (dst_rel)
{
dst_rel = svn_path_uri_decode (dst_rel, pool);
dst_pieces = svn_path_decompose (dst_rel, pool);
if ((! dst_pieces) || (! dst_pieces->nelts))
return svn_error_createf
(SVN_ERR_WC_PATH_NOT_FOUND, 0, NULL, pool,
"error decomposing relative path `%s'", dst_rel);
}
/* Allocate room for the root baton, the pieces of the
source's or destination's path, and the destination itself. */
{
int num, num2;
num = src_pieces ? src_pieces->nelts : 0;
if (((num2 = (dst_pieces ? dst_pieces->nelts : 0))) > num)
num = num2;
batons = apr_palloc (pool, sizeof (void *) * (num + 2));
}
/* Get the RA vtable that matches URL. */
SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, top_url, pool));
/* Open an RA session for the URL. Note that we don't have a local
directory, nor a place to put temp files or store the auth data. */
SVN_ERR (svn_client__open_ra_session (&sess, ra_lib, top_url, NULL, NULL,
NULL, FALSE, FALSE, TRUE,
auth_baton, pool));
/* Pass null for the path, to ensure error if trying to get a
revision based on the working copy. */
SVN_ERR (svn_client__get_revision_number
(&src_revnum, ra_lib, sess, src_revision, NULL, pool));
SVN_ERR (ra_lib->get_latest_revnum (sess, &youngest));
/* Use YOUNGEST for copyfrom args if not provided. */
if (! SVN_IS_VALID_REVNUM (src_revnum))
src_revnum = youngest;
/* Verify that SRC_URL exists in the repository. */
SVN_ERR (ra_lib->check_path (&src_kind, sess, src_rel, src_revnum));
if (src_kind == svn_node_none)
return svn_error_createf
(SVN_ERR_FS_NOT_FOUND, 0, NULL, pool,
"path `%s' does not exist in revision `%" SVN_REVNUM_T_FMT "'",
src_url, src_revnum);
/* Figure out the basename that will result from this operation. */
SVN_ERR (ra_lib->check_path (&dst_kind, sess, dst_rel, youngest));
if ((dst_kind == svn_node_none)
|| (dst_kind == svn_node_file))
{
svn_path_split_nts (dst_url, &unused, &base_name, pool);
if (dst_pieces)
dst_pieces->nelts--; /* hack - where's apr_array_pop()? */
}
else if (dst_kind == svn_node_dir)
{
/* As a matter of client-side policy, we prevent overwriting any
pre-existing directory. So we temporarily append src_url's
basename to dst_rel, and see if that already exists. */
svn_node_kind_t some_kind;
const char *hypothetical_repos_path;
base_name = svn_path_basename (src_url, pool);
hypothetical_repos_path
= svn_path_join (dst_rel, svn_path_uri_decode (base_name, pool), pool);
SVN_ERR (ra_lib->check_path (&some_kind, sess,
hypothetical_repos_path, youngest));
if (some_kind != svn_node_none)
return svn_error_createf (SVN_ERR_FS_ALREADY_EXISTS, 0, NULL, pool,
"fs path `%s' already exists.",
hypothetical_repos_path);
}
else
{
return svn_error_createf (SVN_ERR_NODE_UNKNOWN_KIND, 0, NULL, pool,
"unrecognized node kind of %s.", dst_url);
}
/* Fetch RA commit editor. */
SVN_ERR (ra_lib->get_commit_editor (sess, &editor, &edit_baton,
&committed_rev,
&committed_date,
&committed_author,
message));
/* ### Avoiding iteration pools in the loops below until we know
they're necessary. If there are performance problems with this
function, that's the first place to look. */
/* Drive that editor, baby! */
SVN_ERR (editor->open_root (edit_baton, youngest, pool, &root_baton));
/* Stuff the root baton here for convenience. */
batons[i] = root_baton;
/* Open directories down to the place where we need to make our
copy. */
telepath = "";
if (dst_pieces && dst_pieces->nelts)
{
/* open_directory() all the way down to DST's parent. */
while (i < dst_pieces->nelts)
{
piece = (((const char **)(dst_pieces)->elts)[i]);
telepath = svn_path_join (telepath, piece, pool);
SVN_ERR (editor->open_directory (telepath, batons[i],
youngest, pool, &(batons[i + 1])));
i++;
}
}
/* Add our file/dir with copyfrom history. */
telepath = svn_path_join (telepath,
svn_path_uri_decode (base_name, pool),
pool);
if (src_kind == svn_node_dir)
{
SVN_ERR (editor->add_directory (telepath, batons[i], src_url,
src_revnum, pool, &baton));
SVN_ERR (editor->close_directory (baton));
}
else
{
SVN_ERR (editor->add_file (telepath, batons[i], src_url,
src_revnum, pool, &baton));
SVN_ERR (editor->close_file (baton));
}
/* Now, close up all those batons (except the root
baton). */
while (i)
{
SVN_ERR (editor->close_directory (batons[i]));
batons[i--] = NULL;
}
/* If this was a move, we need to remove the SRC_URL. */
telepath = "";
if (is_move)
{
/* If SRC_PIECES is NULL, we're trying to move a directory into
itself (or one of its chidren...we should have caught that by
now). */
assert (src_pieces != NULL);
/* open_directory() all the way down to SRC's parent. */
while (i < (src_pieces->nelts - 1))
{
piece = (((const char **)(src_pieces)->elts)[i]);
telepath = svn_path_join (telepath, piece, pool);
SVN_ERR (editor->open_directory (telepath, batons[i],
youngest, pool, &(batons[i + 1])));
i++;
}
/* Delete SRC. */
piece = (((const char **)(src_pieces)->elts)[i]);
telepath = svn_path_join (telepath, piece, pool);
SVN_ERR (editor->delete_entry (telepath, SVN_INVALID_REVNUM,
batons[i], pool));
/* Now, close up all those batons (except the root
baton). */
while (i)
{
SVN_ERR (editor->close_directory (batons[i--]));
}
}
/* Turn off the lights, close up the shop, and go home. */
SVN_ERR (editor->close_directory (batons[0]));
SVN_ERR (editor->close_edit (edit_baton));
/* Fill in the commit_info structure. */
*commit_info = svn_client__make_commit_info (committed_rev,
committed_author,
committed_date,
pool);
SVN_ERR (ra_lib->close (sess));
return SVN_NO_ERROR;
}
static svn_error_t *
remove_tmpfiles (apr_hash_t *tempfiles,
apr_pool_t *pool)
{
apr_hash_index_t *hi;
/* Split if there's nothing to be done. */
if (! tempfiles)
return SVN_NO_ERROR;
/* Clean up any tempfiles. */
for (hi = apr_hash_first (pool, tempfiles); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t keylen;
void *val;
svn_node_kind_t kind;
apr_hash_this (hi, &key, &keylen, &val);
SVN_ERR (svn_io_check_path ((const char *)key, &kind, pool));
if (kind == svn_node_file)
SVN_ERR (svn_io_remove_file ((const char *)key, pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
reconcile_errors (svn_error_t *commit_err,
svn_error_t *unlock_err,
svn_error_t *cleanup_err,
apr_pool_t *pool)
{
svn_error_t *err;
/* Early release (for good behavior). */
if (! (commit_err || unlock_err || cleanup_err))
return SVN_NO_ERROR;
/* If there was a commit error, start off our error chain with
that. */
if (commit_err)
{
commit_err = svn_error_quick_wrap
(commit_err, "Commit failed (details follow):");
err = commit_err;
}
/* Else, create a new "general" error that will lead off the errors
that follow. */
else
err = svn_error_create (SVN_ERR_BASE, 0, NULL, pool,
"Commit succeeded, but other errors follow:");
/* If there was an unlock error... */
if (unlock_err)
{
/* Wrap the error with some headers. */
unlock_err = svn_error_quick_wrap
(unlock_err, "Error unlocking locked dirs (details follow):");
/* Append this error to the chain. */
svn_error_compose (err, unlock_err);
}
/* If there was a cleanup error... */
if (cleanup_err)
{
/* Wrap the error with some headers. */
cleanup_err = svn_error_quick_wrap
(cleanup_err, "Error in post-commit clean-up (details follow):");
/* Append this error to the chain. */
svn_error_compose (err, cleanup_err);
}
return err;
}
static svn_error_t *
wc_to_repos_copy (svn_client_commit_info_t **commit_info,
const char *src_path,
const char *dst_url,
svn_client_auth_baton_t *auth_baton,
const char *message,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
const char *anchor, *target, *parent, *base_name;
void *ra_baton, *session;
svn_ra_plugin_t *ra_lib;
const svn_delta_editor_t *editor;
void *edit_baton;
svn_node_kind_t src_kind, dst_kind;
svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
const char *committed_date = NULL;
const char *committed_author = NULL;
apr_hash_t *committables, *tempfiles = NULL;
svn_wc_adm_access_t *adm_access;
apr_array_header_t *commit_items;
svn_error_t *cmt_err = NULL, *unlock_err = NULL, *cleanup_err = NULL;
svn_boolean_t commit_in_progress = FALSE;
const char *base_path;
const char *base_url;
/* Check the SRC_PATH. */
SVN_ERR (svn_io_check_path (src_path, &src_kind, pool));
/* Split the SRC_PATH into a parent and basename. */
svn_path_split_nts (src_path, &parent, &base_name, pool);
SVN_ERR (svn_wc_adm_open (&adm_access, NULL, parent, FALSE, TRUE, pool));
/* Split the DST_URL into an anchor and target. */
svn_path_split_nts (dst_url, &anchor, &target, pool);
/* Get the RA vtable that matches URL. */
SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, anchor, pool));
/* Open an RA session for the anchor URL. */
SVN_ERR (svn_client__open_ra_session (&session, ra_lib, anchor, parent,
adm_access, NULL, TRUE, TRUE, TRUE,
auth_baton, pool));
/* Figure out the basename that will result from this operation. */
SVN_ERR (ra_lib->check_path (&dst_kind, session,
svn_path_uri_decode (target, pool),
SVN_INVALID_REVNUM));
/* Close the RA session. We'll re-open it after we've figured out
the right URL to open. */
SVN_ERR (ra_lib->close (session));
session = NULL;
/* BASE_URL defaults to DST_URL. */
base_url = apr_pstrdup (pool, dst_url);
if (dst_kind == svn_node_none)
{
/* DST_URL doesn't exist under it's parent URL, so the URL we
will be creating is DST_URL. */
}
else if (dst_kind == svn_node_dir)
{
/* DST_URL is an existing directory URL. The URL we will be
creating, then, is DST_URL+BASENAME. */
base_url = svn_path_url_add_component (base_url, base_name, pool);
}
else
{
/* DST_URL is an existing file, which can't be overwritten or
used as a container, so error out. */
return svn_error_createf (SVN_ERR_FS_ALREADY_EXISTS, 0, NULL, pool,
"file `%s' already exists.", dst_url);
}
/* Get the absolute path of the WC path. */
SVN_ERR (svn_path_get_absolute (&base_path, src_path, pool));
/* Crawl the working copy for commit items. */
if ((cmt_err = svn_client__get_copy_committables (&committables,
base_url,
base_path,
adm_access,
pool)))
goto cleanup;
/* ### todo: There should be only one hash entry, which currently
has a hacked name until we have the entries files storing
canonical repository URLs. Then, the hacked name can go away and
be replaced with a entry->repos (or whereever the entry's
canonical repos URL is stored). */
if (! ((commit_items = apr_hash_get (committables,
SVN_CLIENT__SINGLE_REPOS_NAME,
APR_HASH_KEY_STRING))))
goto cleanup;
/* Sort and condense our COMMIT_ITEMS. */
if ((cmt_err = svn_client__condense_commit_items (&base_url,
commit_items,
pool)))
goto cleanup;
/* Open an RA session to BASE_URL. */
if ((cmt_err = svn_client__open_ra_session (&session, ra_lib, base_url, NULL,
NULL, commit_items, TRUE, TRUE,
TRUE, auth_baton, pool)))
goto cleanup;
/* Fetch RA commit editor. */
if ((cmt_err = ra_lib->get_commit_editor (session, &editor, &edit_baton,
&committed_rev, &committed_date,
&committed_author, message)))
goto cleanup;
/* Make a note that we have a commit-in-progress. */
commit_in_progress = TRUE;
/* Perform the commit. */
cmt_err = svn_client__do_commit (base_url, commit_items, adm_access,
editor, edit_baton,
notify_func, notify_baton,
0, /* ### any notify_path_offset needed? */
&tempfiles, pool);
commit_in_progress = FALSE;
/* Sleep for one second to ensure timestamp integrity. */
apr_sleep (APR_USEC_PER_SEC * 1);
cleanup:
/* Abort the commit if it is still in progress. */
if (commit_in_progress)
editor->abort_edit (edit_baton); /* ignore return value */
/* We were committing to RA, so close the session. */
if (session)
ra_lib->close (session);
/* ### Under what conditions should we remove the locks? */
unlock_err = svn_wc_adm_close (adm_access);
/* Remove any outstanding temporary text-base files. */
if (tempfiles)
cleanup_err = remove_tmpfiles (tempfiles, pool);
/* Fill in the commit_info structure */
*commit_info = svn_client__make_commit_info (committed_rev,
committed_author,
committed_date, pool);
return reconcile_errors (cmt_err, unlock_err, cleanup_err, pool);
}
/*
### 838 OPTIONAL_ADM_ACCESS is an access baton with a write lock for the
### parent of DST_PATH. This parameter should be removed when issue 838
### stops using svn_client_copy.
*/
static svn_error_t *
repos_to_wc_copy (const char *src_url,
const svn_client_revision_t *src_revision,
const char *dst_path,
svn_wc_adm_access_t *optional_adm_access,
svn_client_auth_baton_t *auth_baton,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
void *ra_baton, *sess;
svn_ra_plugin_t *ra_lib;
svn_node_kind_t src_kind, dst_kind;
svn_revnum_t src_revnum;
svn_wc_adm_access_t *adm_access;
apr_hash_t *props = NULL;
apr_hash_index_t *hi;
/* Get the RA vtable that matches URL. */
SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, src_url, pool));
/* Open a repository session to the given URL. We do not (yet) have a
working copy, so we don't have a corresponding path and tempfiles
cannot go into the admin area. We do want to store the resulting
auth data, though, once the WC is built. */
SVN_ERR (svn_client__open_ra_session (&sess, ra_lib, src_url, NULL, NULL,
NULL, TRUE, FALSE, TRUE,
auth_baton, pool));
/* Pass null for the path, to ensure error if trying to get a
revision based on the working copy. */
SVN_ERR (svn_client__get_revision_number
(&src_revnum, ra_lib, sess, src_revision, NULL, pool));
/* Verify that SRC_URL exists in the repository. */
SVN_ERR (ra_lib->check_path (&src_kind, sess, "", src_revnum));
if (src_kind == svn_node_none)
{
if (SVN_IS_VALID_REVNUM (src_revnum))
return svn_error_createf
(SVN_ERR_FS_NOT_FOUND, 0, NULL, pool,
"path `%s' not found in revision `%" SVN_REVNUM_T_FMT "'",
src_url, src_revnum);
else
return svn_error_createf
(SVN_ERR_FS_NOT_FOUND, 0, NULL, pool,
"path `%s' not found in head revision", src_url);
}
/* There are two interfering sets of cases to watch out for here:
*
* First set:
*
* 1) If DST_PATH does not exist, then great. We're going to
* create a new entry in its parent.
* 2) If it does exist, then it must be a directory and we're
* copying to a new entry inside that dir (the entry's name is
* the basename of SRC_URL).
*
* But while that's all going on, we must also remember:
*
* A) If SRC_URL is a directory in the repository, we can check
* it out directly, no problem.
* B) If SRC_URL is a file, we have to manually get the editor
* started, since there won't be a root to open.
*
* I'm going to ignore B for the moment, and implement cases 1 and
* 2 under A.
*/
/* First, figure out about dst. */
SVN_ERR (svn_io_check_path (dst_path, &dst_kind, pool));
if (dst_kind == svn_node_dir)
{
const char *base_name;
svn_path_split_nts (src_url, NULL, &base_name, pool);
dst_path = svn_path_join (dst_path,
svn_path_uri_decode (base_name, pool),
pool);
}
else if (dst_kind != svn_node_none) /* must be a file */
{
return svn_error_createf (SVN_ERR_ENTRY_EXISTS, 0, NULL, pool,
"file `%s' already exists.", dst_path);
}
/* Now that dst_path has possibly been reset, check that there's
nothing in the way of the upcoming checkout. */
SVN_ERR (svn_io_check_path (dst_path, &dst_kind, pool));
if (dst_kind != svn_node_none)
return svn_error_createf (SVN_ERR_WC_OBSTRUCTED_UPDATE, 0, NULL, pool,
"`%s' is in the way", dst_path);
if (! optional_adm_access)
SVN_ERR (svn_wc_adm_probe_open (&adm_access, NULL, dst_path, TRUE, FALSE,
pool));
else
adm_access = optional_adm_access;
if (src_kind == svn_node_dir)
{
const svn_delta_editor_t *editor;
void *edit_baton;
/* Get a checkout editor and wrap it. */
SVN_ERR (svn_wc_get_checkout_editor (dst_path, src_url, src_revnum, 1,
notify_func, notify_baton,
&editor, &edit_baton,
NULL, pool));
/* Check out the new tree. The parent dir will get no entry, so
it will be as if the new tree isn't really there yet. */
SVN_ERR (ra_lib->do_checkout (sess, src_revnum, 1,
editor,
edit_baton));
if (! SVN_IS_VALID_REVNUM (src_revnum))
{
/* If we just checked out from the "head" revision, that's fine,
but we don't want to pass a '-1' as a copyfrom_rev to
svn_wc_add(). That function will dump it right into the
entry, and when we try to commit later on, the
'add-dir-with-history' step will be -very- unhappy; it only
accepts specific revisions.
On the other hand, we *could* say that -1 is a legitimate
copyfrom_rev, but I think that's bogus. Somebody made a copy
from a particular revision; if they wait a long time to
commit, it would be terrible if the copied happened from a
newer revision!! */
/* We just did a checkout; whatever revision we just got, that
should be the copyfrom_revision when we commit later. */
const svn_wc_entry_t *d_entry;
svn_wc_adm_access_t *dst_access;
SVN_ERR (svn_wc_adm_open (&dst_access, adm_access, dst_path,
TRUE, TRUE, pool));
SVN_ERR (svn_wc_entry (&d_entry, dst_path, dst_access, FALSE, pool));
src_revnum = d_entry->revision;
}
} /* end directory case */
else if (src_kind == svn_node_file)
{
apr_status_t status;
svn_stream_t *fstream;
apr_file_t *fp;
svn_revnum_t fetched_rev = 0;
/* Open DST_PATH for writing. */
SVN_ERR_W (svn_io_file_open (&fp, dst_path,
(APR_CREATE | APR_WRITE),
APR_OS_DEFAULT, pool),
"failed to open file for writing.");
/* Create a generic stream that operates on this file. */
fstream = svn_stream_from_aprfile (fp, pool);
/* Have the RA layer 'push' data at this stream. We pass a
relative path of "", because we opened SRC_URL, which is
already the full URL to the file. */
SVN_ERR (ra_lib->get_file (sess, "", src_revnum, fstream,
&fetched_rev, &props));
/* Close the file. */
status = apr_file_close (fp);
if (status)
return svn_error_createf (status, 0, NULL, pool,
"failed to close file '%s'.",
dst_path);
/* Also, if SRC_REVNUM is invalid ('head'), then FETCHED_REV is now
equal to the revision that was actually retrieved. This is
the value we want to use as 'copyfrom_rev' in the call to
svn_wc_add() below. */
if (! SVN_IS_VALID_REVNUM (src_revnum))
src_revnum = fetched_rev;
}
/* Free the RA session. */
SVN_ERR (ra_lib->close (sess));
/* Schedule the new item for addition-with-history.
If the new item is a directory, the URLs will be recursively
rewritten, wcprops removed, and everything marked as 'copied'.
See comment in svn_wc_add()'s doc about whether svn_wc_add is the
appropriate place for this. */
SVN_ERR (svn_wc_add (dst_path, adm_access, src_url, src_revnum,
notify_func, notify_baton, pool));
/* If any properties were fetched (in the file case), apply those
changes now. */
if (props)
{
for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
{
const void *key;
void *val;
enum svn_prop_kind kind;
apr_hash_this (hi, &key, NULL, &val);
/* We only want to set 'normal' props. For now, we're
ignoring any wc props (they're not needed when we commit
an addition), and we're ignoring entry props (they're
written to the entries file as part of the post-commit
processing). */
kind = svn_property_kind (NULL, key);
if (kind == svn_prop_regular_kind)
SVN_ERR (svn_wc_prop_set (key, val, dst_path, adm_access, pool));
}
}
if (! optional_adm_access)
SVN_ERR (svn_wc_adm_close (adm_access));
return SVN_NO_ERROR;
}
/*
### 838 OPTIONAL_ADM_ACCESS is an access baton with a write lock for the
### parent of DST_PATH. This parameter should be removed when issue 838
### stops using svn_client_copy.
*/
static svn_error_t *
setup_copy (svn_client_commit_info_t **commit_info,
const char *src_path,
const svn_client_revision_t *src_revision,
const char *dst_path,
svn_wc_adm_access_t *optional_adm_access,
svn_client_auth_baton_t *auth_baton,
svn_client_get_commit_log_t log_msg_func,
void *log_msg_baton,
svn_boolean_t is_move,
svn_boolean_t force,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
svn_boolean_t src_is_url, dst_is_url;
const char *message;
/* Are either of our paths URLs? */
src_is_url = svn_path_is_url (src_path);
dst_is_url = svn_path_is_url (dst_path);
if (svn_path_is_child (src_path, dst_path, pool))
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"cannot copy path '%s' into its own child '%s'",
src_path, dst_path);
if (is_move)
{
if (src_is_url == dst_is_url)
{
if (strcmp (src_path, dst_path) == 0)
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"cannot move path '%s' into itself",
src_path);
}
else
{
/* Disallow moves between the working copy and the repository. */
return svn_error_create
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"no support for repos <--> working copy moves");
}
/* It doesn't make sense to specify revisions in a move. */
/* ### todo: this check could fail wrongly. For example,
someone could pass in an svn_client_revision_number that just
happens to be the HEAD. It's fair enough to punt then, IMHO,
and just demand that the user not specify a revision at all;
beats mucking up this function with RA calls and such. */
if (src_revision->kind != svn_client_revision_unspecified
&& src_revision->kind != svn_client_revision_head)
{
return svn_error_create
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"cannot specify revisions with move operations");
}
}
else
{
if (!src_is_url)
{
if (src_revision->kind != svn_client_revision_unspecified
&& src_revision->kind != svn_client_revision_head)
{
/* We can convert the working copy path to a URL based on the
entries file. */
svn_wc_adm_access_t *adm_access; /* ### FIXME local */
const svn_wc_entry_t *entry;
SVN_ERR (svn_wc_adm_probe_open (&adm_access, NULL, src_path,
FALSE, FALSE, pool));
SVN_ERR (svn_wc_entry (&entry, src_path, adm_access, FALSE,
pool));
SVN_ERR (svn_wc_adm_close (adm_access));
src_path = entry->url;
src_is_url = TRUE;
}
}
}
/* Create a new commit item and add it to the array. */
if (dst_is_url && log_msg_func)
{
svn_client_commit_item_t *item;
apr_array_header_t *commit_items
= apr_array_make (pool, 1, sizeof (item));
item = apr_pcalloc (pool, sizeof (*item));
item->url = apr_pstrdup (pool, dst_path);
item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
(*((svn_client_commit_item_t **) apr_array_push (commit_items)))
= item;
SVN_ERR ((*log_msg_func) (&message, commit_items,
log_msg_baton, pool));
if (! message)
return SVN_NO_ERROR;
}
else
message = "";
/* Now, call the right handler for the operation. */
if ((! src_is_url) && (! dst_is_url))
SVN_ERR (wc_to_wc_copy (src_path, dst_path, optional_adm_access,
is_move, force,
notify_func, notify_baton,
pool));
else if ((! src_is_url) && (dst_is_url))
SVN_ERR (wc_to_repos_copy (commit_info, src_path, dst_path,
auth_baton, message,
notify_func, notify_baton,
pool));
else if ((src_is_url) && (! dst_is_url))
SVN_ERR (repos_to_wc_copy (src_path, src_revision,
dst_path, optional_adm_access, auth_baton,
notify_func, notify_baton,
pool));
else
SVN_ERR (repos_to_repos_copy (commit_info, src_path, src_revision,
dst_path, auth_baton, message, is_move,
pool));
return SVN_NO_ERROR;
}
/* Public Interfaces */
/*
### 838 OPTIONAL_ADM_ACCESS is an access baton with a write lock for the
### parent of DST_PATH. This parameter should be removed when issue 838
### stops using svn_client_copy.
*/
svn_error_t *
svn_client_copy (svn_client_commit_info_t **commit_info,
const char *src_path,
const svn_client_revision_t *src_revision,
const char *dst_path,
svn_wc_adm_access_t *optional_adm_access,
svn_client_auth_baton_t *auth_baton,
svn_client_get_commit_log_t log_msg_func,
void *log_msg_baton,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
return setup_copy (commit_info,
src_path, src_revision, dst_path, optional_adm_access,
auth_baton,
log_msg_func, log_msg_baton,
FALSE /* is_move */,
TRUE /* force, set to avoid deletion check */,
notify_func, notify_baton,
pool);
}
svn_error_t *
svn_client_move (svn_client_commit_info_t **commit_info,
const char *src_path,
const svn_client_revision_t *src_revision,
const char *dst_path,
svn_boolean_t force,
svn_client_auth_baton_t *auth_baton,
svn_client_get_commit_log_t log_msg_func,
void *log_msg_baton,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
return setup_copy (commit_info,
src_path, src_revision, dst_path, NULL, auth_baton,
log_msg_func, log_msg_baton,
TRUE /* is_move */,
force,
notify_func, notify_baton,
pool);
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */