diff options
Diffstat (limited to 'subversion/libsvn_client/wc_editor.c')
-rw-r--r-- | subversion/libsvn_client/wc_editor.c | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/subversion/libsvn_client/wc_editor.c b/subversion/libsvn_client/wc_editor.c new file mode 100644 index 0000000000000..145fce05650e3 --- /dev/null +++ b/subversion/libsvn_client/wc_editor.c @@ -0,0 +1,655 @@ +/* + * wc_editor.c: editing the local modifications in the WC. + * + * ==================================================================== + * 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_pools.h" +#include "svn_props.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" + + +/* ------------------------------------------------------------------ */ + +/* WC Modifications Editor. + * + * This editor applies incoming modifications onto the current working state + * of the working copy, to produce a new working state. + * + * Currently, it assumes the working state matches what the edit driver + * expects to find, and may throw an error if not. + * + * For simplicity, we apply incoming edits as they arrive, rather than + * queueing them up to apply in a batch. + * + * TODO: + * - tests + * - use for all existing scenarios ('svn add', 'svn propset', etc.) + * - Instead of 'root_dir_add' option, probably the driver should anchor + * at the parent dir. + * - Instead of 'ignore_mergeinfo' option, implement that as a wrapper. + * - Option to quietly accept changes that seem to be already applied + * in the versioned state and/or on disk. + * Consider 'svn add' which assumes items to be added are found on disk. + * - Notification. + */ + +/* Everything we need to know about the edit session. + */ +struct edit_baton_t +{ + const char *anchor_abspath; + svn_boolean_t manage_wc_write_lock; + const char *lock_root_abspath; /* the path locked, when locked */ + + /* True => 'open_root' method will act as 'add_directory' */ + svn_boolean_t root_dir_add; + /* True => filter out any incoming svn:mergeinfo property changes */ + svn_boolean_t ignore_mergeinfo_changes; + + svn_ra_session_t *ra_session; + + svn_wc_context_t *wc_ctx; + svn_client_ctx_t *ctx; + svn_wc_notify_func2_t notify_func; + void *notify_baton; +}; + +/* Everything we need to know about a directory that's open for edits. + */ +struct dir_baton_t +{ + apr_pool_t *pool; + + struct edit_baton_t *eb; + + const char *local_abspath; +}; + +/* Join PATH onto ANCHOR_ABSPATH. + * Throw an error if the result is outside ANCHOR_ABSPATH. + */ +static svn_error_t * +get_path(const char **local_abspath_p, + const char *anchor_abspath, + const char *path, + apr_pool_t *result_pool) +{ + svn_boolean_t under_root; + + SVN_ERR(svn_dirent_is_under_root(&under_root, local_abspath_p, + anchor_abspath, path, result_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, result_pool)); + } + return SVN_NO_ERROR; +} + +/* Create a directory on disk and add it to version control, + * with no properties. + */ +static svn_error_t * +mkdir(const char *abspath, + struct edit_baton_t *eb, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_io_make_dir_recursively(abspath, scratch_pool)); + SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath, + NULL /*properties*/, + TRUE /* skip checks */, + eb->notify_func, eb->notify_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* Prepare to open or add a directory: initialize a new dir baton. + * + * If PATH is "" and PB is null, it represents the root directory of + * the edit; otherwise PATH is not "" and PB is not null. + */ +static svn_error_t * +dir_open_or_add(struct dir_baton_t **child_dir_baton, + const char *path, + struct dir_baton_t *pb, + struct edit_baton_t *eb, + apr_pool_t *dir_pool) +{ + struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); + + db->pool = dir_pool; + db->eb = eb; + + SVN_ERR(get_path(&db->local_abspath, + eb->anchor_abspath, path, dir_pool)); + + *child_dir_baton = db; + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +release_write_lock(struct edit_baton_t *eb, + apr_pool_t *scratch_pool) +{ + if (eb->lock_root_abspath) + { + SVN_ERR(svn_wc__release_write_lock( + eb->ctx->wc_ctx, eb->lock_root_abspath, scratch_pool)); + eb->lock_root_abspath = NULL; + } + return SVN_NO_ERROR; +} + +/* */ +static apr_status_t +pool_cleanup_handler(void *root_baton) +{ + struct dir_baton_t *db = root_baton; + struct edit_baton_t *eb = db->eb; + + svn_error_clear(release_write_lock(eb, db->pool)); + return APR_SUCCESS; +} + +/* 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; + struct dir_baton_t *db; + + SVN_ERR(dir_open_or_add(&db, "", NULL, eb, result_pool)); + + /* Acquire a WC write lock */ + if (eb->manage_wc_write_lock) + { + apr_pool_cleanup_register(db->pool, db, + pool_cleanup_handler, + apr_pool_cleanup_null); + SVN_ERR(svn_wc__acquire_write_lock(&eb->lock_root_abspath, + eb->ctx->wc_ctx, + eb->anchor_abspath, + FALSE /*lock_anchor*/, + db->pool, db->pool)); + } + + if (eb->root_dir_add) + { + SVN_ERR(mkdir(db->local_abspath, eb, result_pool)); + } + + *root_baton = db; + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function */ +static svn_error_t * +edit_close_or_abort(void *edit_baton, + apr_pool_t *scratch_pool) +{ + SVN_ERR(release_write_lock(edit_baton, scratch_pool)); + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t revision, + void *parent_baton, + apr_pool_t *scratch_pool) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + const char *local_abspath; + + SVN_ERR(get_path(&local_abspath, + eb->anchor_abspath, path, scratch_pool)); + SVN_ERR(svn_wc_delete4(eb->wc_ctx, local_abspath, + FALSE /*keep_local*/, + TRUE /*delete_unversioned*/, + NULL, NULL, /*cancellation*/ + eb->notify_func, eb->notify_baton, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +dir_open(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + void **child_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct dir_baton_t *db; + + SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool)); + + *child_baton = db; + 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; + struct dir_baton_t *db; + /* ### Our caller should be providing a scratch pool */ + apr_pool_t *scratch_pool = svn_pool_create(result_pool); + + SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool)); + + if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision)) + { + SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/, + svn_node_dir, + copyfrom_path, + copyfrom_revision, + db->local_abspath, + db->eb->ra_session, + db->eb->ctx, + scratch_pool)); + } + else + { + SVN_ERR(mkdir(db->local_abspath, eb, result_pool)); + } + + *child_baton = db; + svn_pool_destroy(scratch_pool); + 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; + + if (svn_property_kind2(name) != svn_prop_regular_kind + || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO))) + { + /* We can't handle DAV, ENTRY and merge specific props here */ + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value, + svn_depth_empty, FALSE, NULL, + NULL, NULL, /* Cancellation */ + NULL, NULL, /* Notification */ + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +dir_close(void *dir_baton, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +/* Everything we need to know about a file that's open for edits. + */ +struct file_baton_t +{ + apr_pool_t *pool; + + struct edit_baton_t *eb; + + const char *local_abspath; + + /* fields for the transfer of text changes */ + const char *writing_file; + unsigned char digest[APR_MD5_DIGESTSIZE]; /* MD5 digest of new fulltext */ + svn_stream_t *wc_file_read_stream, *tmp_file_write_stream; + const char *tmp_path; +}; + +/* Create a new file on disk and add it to version control. + * + * The file is empty and has no properties. + */ +static svn_error_t * +mkfile(const char *abspath, + struct edit_baton_t *eb, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_io_file_create_empty(abspath, scratch_pool)); + SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath, + NULL /*properties*/, + TRUE /* skip checks */, + eb->notify_func, eb->notify_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +file_open_or_add(const char *path, + void *parent_baton, + struct file_baton_t **file_baton, + apr_pool_t *file_pool) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb)); + + fb->pool = file_pool; + fb->eb = eb; + SVN_ERR(get_path(&fb->local_abspath, + eb->anchor_abspath, path, fb->pool)); + + *file_baton = fb; + return SVN_NO_ERROR; +} + +static svn_error_t * +file_open(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + void **file_baton) +{ + struct file_baton_t *fb; + + SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool)); + + *file_baton = fb; + return SVN_NO_ERROR; +} + +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 file_baton_t *fb; + + SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool)); + + if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision)) + { + SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/, + svn_node_file, + copyfrom_path, + copyfrom_revision, + fb->local_abspath, + fb->eb->ra_session, + fb->eb->ctx, fb->pool)); + } + else + { + SVN_ERR(mkfile(fb->local_abspath, fb->eb, result_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; + struct edit_baton_t *eb = fb->eb; + + if (svn_property_kind2(name) != svn_prop_regular_kind + || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO))) + { + /* We can't handle DAV, ENTRY and merge specific props here */ + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, fb->local_abspath, name, value, + svn_depth_empty, FALSE, NULL, + NULL, NULL, /* Cancellation */ + NULL, NULL, /* Notification */ + scratch_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; + const char *target_dir = svn_dirent_dirname(fb->local_abspath, fb->pool); + svn_error_t *err; + + SVN_ERR_ASSERT(! fb->writing_file); + + err = svn_stream_open_readonly(&fb->wc_file_read_stream, fb->local_abspath, + fb->pool, fb->pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + fb->wc_file_read_stream = svn_stream_empty(fb->pool); + } + else + SVN_ERR(err); + + SVN_ERR(svn_stream_open_unique(&fb->tmp_file_write_stream, &fb->writing_file, + target_dir, svn_io_file_del_none, + fb->pool, fb->pool)); + + svn_txdelta_apply(fb->wc_file_read_stream, + fb->tmp_file_write_stream, + 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; + + /* If we have text changes, write them to disk */ + if (fb->writing_file) + { + SVN_ERR(svn_stream_close(fb->wc_file_read_stream)); + SVN_ERR(svn_io_file_rename2(fb->writing_file, fb->local_abspath, + FALSE /*flush*/, scratch_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))); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__wc_editor_internal(const svn_delta_editor_t **editor_p, + void **edit_baton_p, + const char *dst_abspath, + svn_boolean_t root_dir_add, + svn_boolean_t ignore_mergeinfo_changes, + svn_boolean_t manage_wc_write_lock, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + svn_delta_editor_t *editor = svn_delta_default_editor(result_pool); + struct edit_baton_t *eb = apr_pcalloc(result_pool, sizeof(*eb)); + + eb->anchor_abspath = apr_pstrdup(result_pool, dst_abspath); + eb->manage_wc_write_lock = manage_wc_write_lock; + eb->lock_root_abspath = NULL; + eb->root_dir_add = root_dir_add; + eb->ignore_mergeinfo_changes = ignore_mergeinfo_changes; + + eb->ra_session = ra_session; + eb->wc_ctx = ctx->wc_ctx; + eb->ctx = ctx; + eb->notify_func = notify_func; + eb->notify_baton = notify_baton; + + editor->open_root = edit_open; + editor->close_edit = edit_close_or_abort; + editor->abort_edit = edit_close_or_abort; + + editor->delete_entry = delete_entry; + + editor->open_directory = dir_open; + editor->add_directory = dir_add; + editor->change_dir_prop = dir_change_prop; + editor->close_directory = dir_close; + + editor->open_file = file_open; + editor->add_file = file_add; + editor->change_file_prop = file_change_prop; + editor->apply_textdelta = file_textdelta; + editor->close_file = file_close; + + *editor_p = editor; + *edit_baton_p = eb; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__wc_editor(const svn_delta_editor_t **editor_p, + void **edit_baton_p, + const char *dst_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + SVN_ERR(svn_client__wc_editor_internal(editor_p, edit_baton_p, + dst_abspath, + FALSE /*root_dir_add*/, + FALSE /*ignore_mergeinfo_changes*/, + TRUE /*manage_wc_write_lock*/, + notify_func, notify_baton, + ra_session, + ctx, result_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__wc_copy_mods(const char *src_wc_abspath, + const char *dst_wc_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client__pathrev_t *base; + const char *dst_wc_url; + svn_ra_session_t *ra_session; + const svn_delta_editor_t *editor; + void *edit_baton; + apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1, + sizeof(char *)); + + /* We'll need an RA session to obtain the base of any copies */ + SVN_ERR(svn_client__wc_node_get_base(&base, + src_wc_abspath, ctx->wc_ctx, + scratch_pool, scratch_pool)); + dst_wc_url = base->url; + SVN_ERR(svn_client_open_ra_session2(&ra_session, + dst_wc_url, dst_wc_abspath, + ctx, scratch_pool, scratch_pool)); + SVN_ERR(svn_client__wc_editor(&editor, &edit_baton, + dst_wc_abspath, + NULL, NULL, /*notification*/ + ra_session, ctx, scratch_pool)); + + APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath; + SVN_ERR(svn_client__wc_replay(src_wc_abspath, + src_targets, svn_depth_infinity, NULL, + editor, edit_baton, + notify_func, notify_baton, + ctx, scratch_pool)); + + return SVN_NO_ERROR; +} |