📄 copy.c
字号:
/*
* copy.c: copy/move wrappers around wc 'copy' functionality.
*
* ====================================================================
* Copyright (c) 2000-2004 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_private_config.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 "svn_opt.h"
#include "svn_time.h"
#include "client.h"
#include "svn_private_config.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.
*/
static svn_error_t *
wc_to_wc_copy (const char *src_path,
const char *dst_path,
svn_boolean_t is_move,
svn_boolean_t force,
svn_client_ctx_t *ctx,
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, NULL,
_("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 (dst_path, &dst_parent, &base_name, pool);
}
else if (dst_kind == svn_node_dir)
{
svn_path_split (src_path, NULL, &base_name, pool);
dst_parent = dst_path;
}
else
return svn_error_createf (SVN_ERR_ENTRY_EXISTS, NULL,
_("File '%s' already exists"), dst_path);
if (is_move)
{
const char *src_parent;
svn_path_split (src_path, &src_parent, NULL, pool);
SVN_ERR (svn_wc_adm_open2 (&src_access, NULL, src_parent, TRUE,
src_kind == svn_node_dir ? -1 : 0,
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
{
const char *src_parent_abs, *dst_parent_abs;
SVN_ERR (svn_path_get_absolute (&src_parent_abs, src_parent, pool));
SVN_ERR (svn_path_get_absolute (&dst_parent_abs, dst_parent, pool));
if ((src_kind == svn_node_dir)
&& (svn_path_is_child (src_parent_abs, dst_parent_abs, pool)))
{
SVN_ERR (svn_wc_adm_retrieve (&adm_access, src_access,
dst_parent, pool));
}
else
{
SVN_ERR (svn_wc_adm_open2 (&adm_access, NULL, dst_parent,
TRUE, 0, pool));
}
}
if (!force)
/* Ensure there are no "awkward" files. */
SVN_ERR_W (svn_client__can_delete (src_path, ctx, pool),
_("Move will not be attempted unless forced"));
}
else
{
SVN_ERR (svn_wc_adm_open2 (&adm_access, NULL, dst_parent, TRUE,
0, pool));
}
/* 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,
ctx->cancel_func, ctx->cancel_baton,
ctx->notify_func, ctx->notify_baton, pool));
if (is_move)
{
SVN_ERR (svn_wc_delete (src_path, src_access,
ctx->cancel_func, ctx->cancel_baton,
ctx->notify_func, ctx->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
{
SVN_ERR (svn_wc_adm_close (adm_access));
}
return SVN_NO_ERROR;
}
struct path_driver_cb_baton
{
const svn_delta_editor_t *editor;
void *edit_baton;
svn_node_kind_t src_kind;
const char *src_url;
const char *src_path;
const char *dst_path;
svn_boolean_t is_move;
svn_boolean_t resurrection;
svn_revnum_t src_revnum;
};
static svn_error_t *
path_driver_cb_func (void **dir_baton,
void *parent_baton,
void *callback_baton,
const char *path,
apr_pool_t *pool)
{
struct path_driver_cb_baton *cb_baton = callback_baton;
svn_boolean_t do_delete = FALSE, do_add = FALSE;
/* Initialize return value. */
*dir_baton = NULL;
/* This function should never get an empty PATH. We can neither
create nor delete the empty PATH, so if someone is calling us
with such, the code is just plain wrong. */
assert (! svn_path_is_empty (path));
/* If this is a resurrection, we know the source and dest paths are
the same, and that our driver will only be calling us once. */
if (cb_baton->resurrection)
{
/* If this is a move, we do nothing. Otherwise, we do the copy. */
if (! cb_baton->is_move)
do_add = TRUE;
}
/* Not a resurrection. */
else
{
/* If this is a move, we check PATH to see if it is the source
or the destination of the move. */
if (cb_baton->is_move)
{
if (strcmp (cb_baton->src_path, path) == 0)
do_delete = TRUE;
else
do_add = TRUE;
}
/* Not a move? This must just be the copy addition. */
else
{
do_add = TRUE;
}
}
if (do_delete)
{
SVN_ERR (cb_baton->editor->delete_entry (path, SVN_INVALID_REVNUM,
parent_baton, pool));
}
if (do_add)
{
if (cb_baton->src_kind == svn_node_file)
{
void *file_baton;
SVN_ERR (cb_baton->editor->add_file (path, parent_baton,
cb_baton->src_url,
cb_baton->src_revnum,
pool, &file_baton));
SVN_ERR (cb_baton->editor->close_file (file_baton, NULL, pool));
}
else
{
SVN_ERR (cb_baton->editor->add_directory (path, parent_baton,
cb_baton->src_url,
cb_baton->src_revnum,
pool, dir_baton));
}
}
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_opt_revision_t *src_revision,
const char *dst_url,
svn_client_ctx_t *ctx,
svn_boolean_t is_move,
apr_pool_t *pool)
{
apr_array_header_t *paths = apr_array_make (pool, 2, sizeof (const char *));
const char *top_url, *src_rel, *dst_rel, *message;
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 *commit_baton;
svn_revnum_t src_revnum;
svn_boolean_t resurrection = FALSE;
struct path_driver_cb_baton cb_baton;
svn_error_t *err;
/* 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) == 0)
{
resurrection = TRUE;
top_url = svn_path_dirname (top_url, pool);
}
/* Get the portions of the SRC and DST URLs that are relative to
TOP_URL, and URI-decode those sections. */
src_rel = svn_path_is_child (top_url, src_url, pool);
if (src_rel)
src_rel = svn_path_uri_decode (src_rel, pool);
else
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);
else
dst_rel = "";
/* We can't move something into itself, period. */
if (svn_path_is_empty (src_rel) && is_move)
return svn_error_createf (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Cannot move URL '%s' into itself"), src_url);
/* Get the RA vtable that matches URL. */
SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
err = svn_ra_get_ra_library (&ra_lib, ra_baton, top_url, pool);
/* If the two URLs appear not to be in the same repository, then
top_url will be empty and the call to svn_ra_get_ra_library()
above will have failed. Below we check for that, and propagate a
descriptive error back to the user.
Ideally, we'd contact the repositories and compare their UUIDs to
determine whether or not src and dst are in the same repository,
instead of depending on an essentially textual comparison.
However, it is simpler to assume that if someone is using the
same repository, then they will use the same hostname/path to
refer to it both times. Conversely, if the repositories are
different, then they can't share a non-empty prefix, so top_url
would still be "" and svn_ra_get_library() would still error.
Thus we can get this check without extra network turnarounds to
fetch the UUIDs.
*/
if (err)
{
if ((err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
&& ((top_url == NULL) || (top_url[0] == '\0')))
{
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Source and dest appear not to be in the same repository "
"(src: '%s'; dst: '%s')"),
src_url, dst_url);
}
else
return err;
}
/* Open an RA session for the URL. Note that we don't have a local
directory, nor a place to put temp files. */
SVN_ERR (svn_client__open_ra_session (&sess, ra_lib, top_url,
NULL,
NULL, NULL, FALSE, TRUE,
ctx, 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));
/* Fetch the youngest revision. */
SVN_ERR (ra_lib->get_latest_revnum (sess, &youngest, pool));
/* 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 (sess, src_rel, src_revnum, &src_kind, pool));
if (src_kind == svn_node_none)
return svn_error_createf
(SVN_ERR_FS_NOT_FOUND, NULL,
_("Path '%s' does not exist in revision %ld"),
src_url, src_revnum);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -