diff options
Diffstat (limited to 'subversion/libsvn_client/copy_foreign.c')
-rw-r--r-- | subversion/libsvn_client/copy_foreign.c | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/subversion/libsvn_client/copy_foreign.c b/subversion/libsvn_client/copy_foreign.c new file mode 100644 index 0000000..8de8a5d --- /dev/null +++ b/subversion/libsvn_client/copy_foreign.c @@ -0,0 +1,571 @@ +/* + * copy_foreign.c: copy from other repository support. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + +/*** Includes. ***/ + +#include <string.h> +#include "svn_hash.h" +#include "svn_client.h" +#include "svn_delta.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_ra.h" +#include "svn_wc.h" + +#include <apr_md5.h> + +#include "client.h" +#include "private/svn_subr_private.h" +#include "private/svn_wc_private.h" +#include "svn_private_config.h" + +struct edit_baton_t +{ + apr_pool_t *pool; + const char *anchor_abspath; + + svn_wc_context_t *wc_ctx; + svn_wc_notify_func2_t notify_func; + void *notify_baton; +}; + +struct dir_baton_t +{ + apr_pool_t *pool; + + struct dir_baton_t *pb; + struct edit_baton_t *eb; + + const char *local_abspath; + + svn_boolean_t created; + apr_hash_t *properties; + + int users; +}; + +/* svn_delta_editor_t function */ +static svn_error_t * +edit_open(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + void **root_baton) +{ + struct edit_baton_t *eb = edit_baton; + apr_pool_t *dir_pool = svn_pool_create(eb->pool); + struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); + + db->pool = dir_pool; + db->eb = eb; + db->users = 1; + db->local_abspath = eb->anchor_abspath; + + SVN_ERR(svn_io_make_dir_recursively(eb->anchor_abspath, dir_pool)); + + *root_baton = db; + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function */ +static svn_error_t * +edit_close(void *edit_baton, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +static svn_error_t * +dir_add(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *result_pool, + void **child_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + apr_pool_t *dir_pool = svn_pool_create(pb->pool); + struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); + svn_boolean_t under_root; + + pb->users++; + + db->pb = pb; + db->eb = pb->eb; + db->pool = dir_pool; + db->users = 1; + + SVN_ERR(svn_dirent_is_under_root(&under_root, &db->local_abspath, + eb->anchor_abspath, path, db->pool)); + if (! under_root) + { + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Path '%s' is not in the working copy"), + svn_dirent_local_style(path, db->pool)); + } + + SVN_ERR(svn_io_make_dir_recursively(db->local_abspath, db->pool)); + + *child_baton = db; + return SVN_NO_ERROR; +} + +static svn_error_t * +dir_change_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct dir_baton_t *db = dir_baton; + struct edit_baton_t *eb = db->eb; + svn_prop_kind_t prop_kind; + + prop_kind = svn_property_kind2(name); + + if (prop_kind != svn_prop_regular_kind + || ! strcmp(name, SVN_PROP_MERGEINFO)) + { + /* We can't handle DAV, ENTRY and merge specific props here */ + return SVN_NO_ERROR; + } + + if (! db->created) + { + /* We can still store them in the hash for immediate addition + with the svn_wc_add_from_disk2() call */ + if (! db->properties) + db->properties = apr_hash_make(db->pool); + + if (value != NULL) + svn_hash_sets(db->properties, apr_pstrdup(db->pool, name), + svn_string_dup(value, db->pool)); + } + else + { + /* We have already notified for this directory, so don't do that again */ + SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value, + svn_depth_empty, FALSE, NULL, + NULL, NULL, /* Cancelation */ + NULL, NULL, /* Notification */ + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Releases the directory baton if there are no more users */ +static svn_error_t * +maybe_done(struct dir_baton_t *db) +{ + db->users--; + + if (db->users == 0) + { + struct dir_baton_t *pb = db->pb; + + svn_pool_clear(db->pool); + + if (pb) + SVN_ERR(maybe_done(pb)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +ensure_added(struct dir_baton_t *db, + apr_pool_t *scratch_pool) +{ + if (db->created) + return SVN_NO_ERROR; + + if (db->pb) + SVN_ERR(ensure_added(db->pb, scratch_pool)); + + db->created = TRUE; + + /* Add the directory with all the already collected properties */ + SVN_ERR(svn_wc_add_from_disk2(db->eb->wc_ctx, + db->local_abspath, + db->properties, + db->eb->notify_func, + db->eb->notify_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +dir_close(void *dir_baton, + apr_pool_t *scratch_pool) +{ + struct dir_baton_t *db = dir_baton; + /*struct edit_baton_t *eb = db->eb;*/ + + SVN_ERR(ensure_added(db, scratch_pool)); + + SVN_ERR(maybe_done(db)); + + return SVN_NO_ERROR; +} + +struct file_baton_t +{ + apr_pool_t *pool; + + struct dir_baton_t *pb; + struct edit_baton_t *eb; + + const char *local_abspath; + apr_hash_t *properties; + + svn_boolean_t writing; + unsigned char digest[APR_MD5_DIGESTSIZE]; + + const char *tmp_path; +}; + +static svn_error_t * +file_add(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *result_pool, + void **file_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + apr_pool_t *file_pool = svn_pool_create(pb->pool); + struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb)); + svn_boolean_t under_root; + + pb->users++; + + fb->pool = file_pool; + fb->eb = eb; + fb->pb = pb; + + SVN_ERR(svn_dirent_is_under_root(&under_root, &fb->local_abspath, + eb->anchor_abspath, path, fb->pool)); + if (! under_root) + { + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Path '%s' is not in the working copy"), + svn_dirent_local_style(path, fb->pool)); + } + + *file_baton = fb; + return SVN_NO_ERROR; +} + +static svn_error_t * +file_change_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct file_baton_t *fb = file_baton; + svn_prop_kind_t prop_kind; + + prop_kind = svn_property_kind2(name); + + if (prop_kind != svn_prop_regular_kind + || ! strcmp(name, SVN_PROP_MERGEINFO)) + { + /* We can't handle DAV, ENTRY and merge specific props here */ + return SVN_NO_ERROR; + } + + /* We store all properties in the hash for immediate addition + with the svn_wc_add_from_disk2() call */ + if (! fb->properties) + fb->properties = apr_hash_make(fb->pool); + + if (value != NULL) + svn_hash_sets(fb->properties, apr_pstrdup(fb->pool, name), + svn_string_dup(value, fb->pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +file_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *result_pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton_t *fb = file_baton; + svn_stream_t *target; + + SVN_ERR_ASSERT(! fb->writing); + + SVN_ERR(svn_stream_open_writable(&target, fb->local_abspath, fb->pool, + fb->pool)); + + fb->writing = TRUE; + svn_txdelta_apply(svn_stream_empty(fb->pool) /* source */, + target, + fb->digest, + fb->local_abspath, + fb->pool, + /* Provide the handler directly */ + handler, handler_baton); + + return SVN_NO_ERROR; +} + +static svn_error_t * +file_close(void *file_baton, + const char *text_checksum, + apr_pool_t *scratch_pool) +{ + struct file_baton_t *fb = file_baton; + struct edit_baton_t *eb = fb->eb; + struct dir_baton_t *pb = fb->pb; + + SVN_ERR(ensure_added(pb, fb->pool)); + + if (text_checksum) + { + svn_checksum_t *expected_checksum; + svn_checksum_t *actual_checksum; + + SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, + text_checksum, fb->pool)); + actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool); + + if (! svn_checksum_match(expected_checksum, actual_checksum)) + return svn_error_trace( + svn_checksum_mismatch_err(expected_checksum, + actual_checksum, + fb->pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style( + fb->local_abspath, + fb->pool))); + } + + SVN_ERR(svn_wc_add_from_disk2(eb->wc_ctx, fb->local_abspath, fb->properties, + eb->notify_func, eb->notify_baton, + fb->pool)); + + svn_pool_destroy(fb->pool); + SVN_ERR(maybe_done(pb)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +copy_foreign_dir(svn_ra_session_t *ra_session, + svn_client__pathrev_t *location, + svn_wc_context_t *wc_ctx, + const char *dst_abspath, + svn_depth_t depth, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + struct edit_baton_t eb; + svn_delta_editor_t *editor = svn_delta_default_editor(scratch_pool); + const svn_delta_editor_t *wrapped_editor; + void *wrapped_baton; + const svn_ra_reporter3_t *reporter; + void *reporter_baton; + + eb.pool = scratch_pool; + eb.anchor_abspath = dst_abspath; + + eb.wc_ctx = wc_ctx; + eb.notify_func = notify_func; + eb.notify_baton = notify_baton; + + editor->open_root = edit_open; + editor->close_edit = edit_close; + + editor->add_directory = dir_add; + editor->change_dir_prop = dir_change_prop; + editor->close_directory = dir_close; + + editor->add_file = file_add; + editor->change_file_prop = file_change_prop; + editor->apply_textdelta = file_textdelta; + editor->close_file = file_close; + + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, + editor, &eb, + &wrapped_editor, &wrapped_baton, + scratch_pool)); + + SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton, + location->rev, "", svn_depth_infinity, + FALSE, FALSE, wrapped_editor, wrapped_baton, + scratch_pool, scratch_pool)); + + SVN_ERR(reporter->set_path(reporter_baton, "", location->rev, depth, + TRUE /* incomplete */, + NULL, scratch_pool)); + + SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_client__copy_foreign(const char *url, + const char *dst_abspath, + svn_opt_revision_t *peg_revision, + svn_opt_revision_t *revision, + svn_depth_t depth, + svn_boolean_t make_parents, + svn_boolean_t already_locked, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_ra_session_t *ra_session; + svn_client__pathrev_t *loc; + svn_node_kind_t kind; + svn_node_kind_t wc_kind; + const char *dir_abspath; + + SVN_ERR_ASSERT(svn_path_is_url(url)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + + /* Do we need to validate/update revisions? */ + + SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc, + url, NULL, + peg_revision, + revision, ctx, + scratch_pool)); + + SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool)); + + if (kind != svn_node_file && kind != svn_node_dir) + return svn_error_createf( + SVN_ERR_ILLEGAL_TARGET, NULL, + _("'%s' is not a valid location inside a repository"), + url); + + SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dst_abspath, FALSE, TRUE, + scratch_pool)); + + if (wc_kind != svn_node_none) + { + return svn_error_createf( + SVN_ERR_ENTRY_EXISTS, NULL, + _("'%s' is already under version control"), + svn_dirent_local_style(dst_abspath, scratch_pool)); + } + + dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); + SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath, + FALSE, FALSE, scratch_pool)); + + if (wc_kind == svn_node_none) + { + if (make_parents) + SVN_ERR(svn_client__make_local_parents(dir_abspath, make_parents, ctx, + scratch_pool)); + + SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath, + FALSE, FALSE, scratch_pool)); + } + + if (wc_kind != svn_node_dir) + return svn_error_createf( + SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("Can't add '%s', because no parent directory is found"), + svn_dirent_local_style(dst_abspath, scratch_pool)); + + + if (kind == svn_node_file) + { + svn_stream_t *target; + apr_hash_t *props; + apr_hash_index_t *hi; + SVN_ERR(svn_stream_open_writable(&target, dst_abspath, scratch_pool, + scratch_pool)); + + SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, target, NULL, &props, + scratch_pool)); + + if (props != NULL) + for (hi = apr_hash_first(scratch_pool, props); hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + + if (svn_property_kind2(name) != svn_prop_regular_kind + || ! strcmp(name, SVN_PROP_MERGEINFO)) + { + /* We can't handle DAV, ENTRY and merge specific props here */ + svn_hash_sets(props, name, NULL); + } + } + + if (!already_locked) + SVN_WC__CALL_WITH_WRITE_LOCK( + svn_wc_add_from_disk2(ctx->wc_ctx, dst_abspath, props, + ctx->notify_func2, ctx->notify_baton2, + scratch_pool), + ctx->wc_ctx, dir_abspath, FALSE, scratch_pool); + else + SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, dst_abspath, props, + ctx->notify_func2, ctx->notify_baton2, + scratch_pool)); + } + else + { + if (!already_locked) + SVN_WC__CALL_WITH_WRITE_LOCK( + copy_foreign_dir(ra_session, loc, + ctx->wc_ctx, dst_abspath, + depth, + ctx->notify_func2, ctx->notify_baton2, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool), + ctx->wc_ctx, dir_abspath, FALSE, scratch_pool); + else + SVN_ERR(copy_foreign_dir(ra_session, loc, + ctx->wc_ctx, dst_abspath, + depth, + ctx->notify_func2, ctx->notify_baton2, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool)); + } + + return SVN_NO_ERROR; +} |