📄 copy.c
字号:
/* * copy.c: copy/move wrappers around wc 'copy' functionality. * * ==================================================================== * Copyright (c) 2000-2006 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_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) * 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, dst_parent_kind; const char *dst_parent, *base_name; svn_wc_adm_access_t *adm_access, *src_access; svn_error_t *err; /* 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"), svn_path_local_style(src_path, pool)); /* If DST_PATH does not exist, then its basename will become a new file or dir added to its parent (possibly an implicit '.'). Else, just error out. */ SVN_ERR(svn_io_check_path(dst_path, &dst_kind, pool)); if (dst_kind != svn_node_none) return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, _("Path '%s' already exists"), svn_path_local_style(dst_path, pool)); svn_path_split(dst_path, &dst_parent, &base_name, pool); /* Make sure the destination parent is a directory and produce a clear error message if it is not. */ SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, pool)); if (dst_parent_kind != svn_node_dir) return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL, _("Path '%s' is not a directory"), svn_path_local_style(dst_parent, pool)); if (is_move) { const char *src_parent; svn_path_split(src_path, &src_parent, NULL, pool); SVN_ERR(svn_wc_adm_open3(&src_access, NULL, src_parent, TRUE, src_kind == svn_node_dir ? -1 : 0, ctx->cancel_func, ctx->cancel_baton, 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_open3(&adm_access, NULL, dst_parent, TRUE, 0, ctx->cancel_func, ctx->cancel_baton,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_open3(&adm_access, NULL, dst_parent, TRUE, 0, ctx->cancel_func, ctx->cancel_baton, 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? */ err = svn_wc_copy2(src_path, adm_access, base_name, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool); svn_sleep_for_timestamps(); SVN_ERR(err); if (is_move) { SVN_ERR(svn_wc_delete2(src_path, src_access, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, 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) { SVN_ERR(svn_path_check_valid(path, pool)); 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_commit_info_t **commit_info_p, 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, *repos_root; svn_revnum_t youngest; svn_ra_session_t *ra_session; 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); } /* Open an RA session for the URL. Note that we don't have a local directory, nor a place to put temp files. */ err = svn_client__open_ra_session_internal(&ra_session, top_url, NULL, NULL, NULL, FALSE, TRUE, ctx, 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_open2() 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; } SVN_ERR(svn_ra_get_repos_root(ra_session, &repos_root, pool)); if (strcmp(dst_url, repos_root) != 0 && svn_path_is_child(dst_url, src_url, pool) != NULL) { resurrection = TRUE; top_url = svn_path_dirname(top_url, pool); SVN_ERR(svn_ra_reparent(ra_session, 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); /* 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_session, src_revision, NULL, pool)); /* Fetch the youngest revision. */ SVN_ERR(svn_ra_get_latest_revnum(ra_session, &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(svn_ra_check_path(ra_session, 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); /* Figure out the basename that will result from this operation. */ SVN_ERR(svn_ra_check_path(ra_session, dst_rel, youngest, &dst_kind, pool)); if (dst_kind != svn_node_none) { /* We disallow the overwriting of existing paths. */ return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, _("Path '%s' already exists"), dst_rel); } /* Create a new commit item and add it to the array. */ if (ctx->log_msg_func || ctx->log_msg_func2) { svn_client_commit_item2_t *item; const char *tmp_file;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -