summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/wc_editor.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/wc_editor.c')
-rw-r--r--subversion/libsvn_client/wc_editor.c655
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;
+}