summaryrefslogtreecommitdiff
path: root/subversion/libsvn_wc/wc_db_update_move.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_wc/wc_db_update_move.c')
-rw-r--r--subversion/libsvn_wc/wc_db_update_move.c2648
1 files changed, 2648 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/wc_db_update_move.c b/subversion/libsvn_wc/wc_db_update_move.c
new file mode 100644
index 0000000..7f4f853
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db_update_move.c
@@ -0,0 +1,2648 @@
+/*
+ * wc_db_update_move.c : updating moves during tree-conflict resolution
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* This file implements an editor and an edit driver which are used
+ * to resolve an "incoming edit, local move-away" tree conflict resulting
+ * from an update (or switch).
+ *
+ * Our goal is to be able to resolve this conflict such that the end
+ * result is just the same as if the user had run the update *before*
+ * the local move.
+ *
+ * When an update (or switch) produces incoming changes for a locally
+ * moved-away subtree, it updates the base nodes of the moved-away tree
+ * and flags a tree-conflict on the moved-away root node.
+ * This editor transfers these changes from the moved-away part of the
+ * working copy to the corresponding moved-here part of the working copy.
+ *
+ * Both the driver and receiver components of the editor are implemented
+ * in this file.
+ *
+ * The driver sees two NODES trees: the move source tree and the move
+ * destination tree. When the move is initially made these trees are
+ * equivalent, the destination is a copy of the source. The source is
+ * a single-op-depth, single-revision, deleted layer [1] and the
+ * destination has an equivalent single-op-depth, single-revision
+ * layer. The destination may have additional higher op-depths
+ * representing adds, deletes, moves within the move destination. [2]
+ *
+ * After the intial move an update has modified the NODES in the move
+ * source and may have introduced a tree-conflict since the source and
+ * destination trees are no longer equivalent. The source is a
+ * different revision and may have text, property and tree changes
+ * compared to the destination. The driver will compare the two NODES
+ * trees and drive an editor to change the destination tree so that it
+ * once again matches the source tree. Changes made to the
+ * destination NODES tree to achieve this match will be merged into
+ * the working files/directories.
+ *
+ * The whole drive occurs as one single wc.db transaction. At the end
+ * of the transaction the destination NODES table should have a layer
+ * that is equivalent to the source NODES layer, there should be
+ * workqueue items to make any required changes to working
+ * files/directories in the move destination, and there should be
+ * tree-conflicts in the move destination where it was not possible to
+ * update the working files/directories.
+ *
+ * [1] The move source tree is single-revision because we currently do
+ * not allow a mixed-rev move, and therefore it is single op-depth
+ * regardless whether it is a base layer or a nested move.
+ *
+ * [2] The source tree also may have additional higher op-depths,
+ * representing a replacement, but this editor only reads from the
+ * single-op-depth layer of it, and makes no changes of any kind
+ * within the source tree.
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include <assert.h>
+
+#include "svn_checksum.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_editor.h"
+
+#include "wc.h"
+#include "props.h"
+#include "wc_db_private.h"
+#include "wc-queries.h"
+#include "conflicts.h"
+#include "workqueue.h"
+#include "token-map.h"
+
+/*
+ * Receiver code.
+ *
+ * The receiver is an editor that, when driven with a certain change, will
+ * merge the edits into the working/actual state of the move destination
+ * at MOVE_ROOT_DST_RELPATH (in struct tc_editor_baton), perhaps raising
+ * conflicts if necessary.
+ *
+ * The receiver should not need to refer directly to the move source, as
+ * the driver should provide all relevant information about the change to
+ * be made at the move destination.
+ */
+
+struct tc_editor_baton {
+ svn_wc__db_t *db;
+ svn_wc__db_wcroot_t *wcroot;
+ const char *move_root_dst_relpath;
+
+ /* The most recent conflict raised during this drive. We rely on the
+ non-Ev2, depth-first, drive for this to make sense. */
+ const char *conflict_root_relpath;
+
+ svn_wc_operation_t operation;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+ apr_pool_t *result_pool; /* For things that live as long as the baton. */
+};
+
+/*
+ * Notifications are delayed until the entire update-move transaction
+ * completes. These functions provide the necessary support by storing
+ * notification information in a temporary db table (the "update_move_list")
+ * and spooling notifications out of that table after the transaction.
+ */
+
+/* Add an entry to the notification list. */
+static svn_error_t *
+update_move_list_add(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc_notify_action_t action,
+ svn_node_kind_t kind,
+ svn_wc_notify_state_t content_state,
+ svn_wc_notify_state_t prop_state)
+
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_UPDATE_MOVE_LIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "sdddd", local_relpath,
+ action, kind, content_state, prop_state));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Send all notifications stored in the notification list, and then
+ * remove the temporary database table. */
+svn_error_t *
+svn_wc__db_update_move_list_notify(svn_wc__db_wcroot_t *wcroot,
+ svn_revnum_t old_revision,
+ svn_revnum_t new_revision,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ if (notify_func)
+ {
+ apr_pool_t *iterpool;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_UPDATE_MOVE_LIST));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *local_relpath;
+ svn_wc_notify_action_t action;
+ svn_wc_notify_t *notify;
+
+ svn_pool_clear(iterpool);
+
+ local_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ action = svn_sqlite__column_int(stmt, 1);
+ notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ iterpool),
+ action, iterpool);
+ notify->kind = svn_sqlite__column_int(stmt, 2);
+ notify->content_state = svn_sqlite__column_int(stmt, 3);
+ notify->prop_state = svn_sqlite__column_int(stmt, 4);
+ notify->old_revision = old_revision;
+ notify->revision = new_revision;
+ notify_func(notify_baton, notify, scratch_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_FINALIZE_UPDATE_MOVE));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Mark a tree-conflict on LOCAL_RELPATH if such a tree-conflict does
+ not already exist. */
+static svn_error_t *
+mark_tree_conflict(const char *local_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_wc__db_t *db,
+ const svn_wc_conflict_version_t *old_version,
+ const svn_wc_conflict_version_t *new_version,
+ const char *move_root_dst_relpath,
+ svn_wc_operation_t operation,
+ svn_node_kind_t old_kind,
+ svn_node_kind_t new_kind,
+ const char *old_repos_relpath,
+ svn_wc_conflict_reason_t reason,
+ svn_wc_conflict_action_t action,
+ const char *move_src_op_root_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_skel_t *conflict;
+ svn_wc_conflict_version_t *conflict_old_version, *conflict_new_version;
+ const char *move_src_op_root_abspath
+ = move_src_op_root_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ move_src_op_root_relpath, scratch_pool)
+ : NULL;
+ const char *old_repos_relpath_part
+ = old_repos_relpath
+ ? svn_relpath_skip_ancestor(old_version->path_in_repos,
+ old_repos_relpath)
+ : NULL;
+ const char *new_repos_relpath
+ = old_repos_relpath_part
+ ? svn_relpath_join(new_version->path_in_repos, old_repos_relpath_part,
+ scratch_pool)
+ : NULL;
+
+ if (!new_repos_relpath)
+ new_repos_relpath
+ = svn_relpath_join(new_version->path_in_repos,
+ svn_relpath_skip_ancestor(move_root_dst_relpath,
+ local_relpath),
+ scratch_pool);
+
+ err = svn_wc__db_read_conflict_internal(&conflict, wcroot, local_relpath,
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ else if (err)
+ {
+ svn_error_clear(err);
+ conflict = NULL;
+ }
+
+ if (conflict)
+ {
+ svn_wc_operation_t conflict_operation;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__conflict_read_info(&conflict_operation, NULL, NULL, NULL,
+ &tree_conflicted,
+ db, wcroot->abspath, conflict,
+ scratch_pool, scratch_pool));
+
+ if (conflict_operation != svn_wc_operation_update
+ && conflict_operation != svn_wc_operation_switch)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' already in conflict"),
+ svn_dirent_local_style(local_relpath,
+ scratch_pool));
+
+ if (tree_conflicted)
+ {
+ svn_wc_conflict_reason_t existing_reason;
+ svn_wc_conflict_action_t existing_action;
+ const char *existing_abspath;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&existing_reason,
+ &existing_action,
+ &existing_abspath,
+ db, wcroot->abspath,
+ conflict,
+ scratch_pool,
+ scratch_pool));
+ if (reason != existing_reason
+ || action != existing_action
+ || (reason == svn_wc_conflict_reason_moved_away
+ && strcmp(move_src_op_root_relpath,
+ svn_dirent_skip_ancestor(wcroot->abspath,
+ existing_abspath))))
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' already in conflict"),
+ svn_dirent_local_style(local_relpath,
+ scratch_pool));
+
+ /* Already a suitable tree-conflict. */
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ conflict = svn_wc__conflict_skel_create(scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ conflict, db,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool),
+ reason,
+ action,
+ move_src_op_root_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ if (reason != svn_wc_conflict_reason_unversioned
+ && old_repos_relpath != NULL /* no local additions */)
+ {
+ conflict_old_version = svn_wc_conflict_version_create2(
+ old_version->repos_url, old_version->repos_uuid,
+ old_repos_relpath, old_version->peg_rev,
+ old_kind, scratch_pool);
+ }
+ else
+ conflict_old_version = NULL;
+
+ conflict_new_version = svn_wc_conflict_version_create2(
+ new_version->repos_url, new_version->repos_uuid,
+ new_repos_relpath, new_version->peg_rev,
+ new_kind, scratch_pool);
+
+ if (operation == svn_wc_operation_update)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(
+ conflict, conflict_old_version, conflict_new_version,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ assert(operation == svn_wc_operation_switch);
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(
+ conflict, conflict_old_version, conflict_new_version,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict, scratch_pool));
+
+ SVN_ERR(update_move_list_add(wcroot, local_relpath,
+ svn_wc_notify_tree_conflict,
+ new_kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ return SVN_NO_ERROR;
+}
+
+/* If LOCAL_RELPATH is a child of the most recently raised
+ tree-conflict or is shadowed then set *IS_CONFLICTED to TRUE and
+ raise a tree-conflict on the root of the obstruction if such a
+ tree-conflict does not already exist. KIND is the kind of the
+ incoming LOCAL_RELPATH. This relies on the non-Ev2, depth-first,
+ drive. */
+static svn_error_t *
+check_tree_conflict(svn_boolean_t *is_conflicted,
+ struct tc_editor_baton *b,
+ const char *local_relpath,
+ svn_node_kind_t old_kind,
+ svn_node_kind_t new_kind,
+ const char *old_repos_relpath,
+ svn_wc_conflict_action_t action,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int dst_op_depth = relpath_depth(b->move_root_dst_relpath);
+ int op_depth;
+ const char *conflict_root_relpath = local_relpath;
+ const char *move_dst_relpath, *dummy1;
+ const char *dummy2, *move_src_op_root_relpath;
+
+ if (b->conflict_root_relpath)
+ {
+ if (svn_relpath_skip_ancestor(b->conflict_root_relpath, local_relpath))
+ {
+ *is_conflicted = TRUE;
+ return SVN_NO_ERROR;
+ }
+ b->conflict_root_relpath = NULL;
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_SELECT_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, local_relpath,
+ dst_op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!have_row)
+ {
+ *is_conflicted = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ *is_conflicted = TRUE;
+
+ while (relpath_depth(conflict_root_relpath) > op_depth)
+ {
+ conflict_root_relpath = svn_relpath_dirname(conflict_root_relpath,
+ scratch_pool);
+ old_kind = new_kind = svn_node_dir;
+ if (old_repos_relpath)
+ old_repos_relpath = svn_relpath_dirname(old_repos_relpath,
+ scratch_pool);
+ action = svn_wc_conflict_action_edit;
+ }
+
+ SVN_ERR(svn_wc__db_op_depth_moved_to(&move_dst_relpath,
+ &dummy1,
+ &dummy2,
+ &move_src_op_root_relpath,
+ dst_op_depth,
+ b->wcroot, conflict_root_relpath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(mark_tree_conflict(conflict_root_relpath,
+ b->wcroot, b->db, b->old_version, b->new_version,
+ b->move_root_dst_relpath, b->operation,
+ old_kind, new_kind,
+ old_repos_relpath,
+ (move_dst_relpath
+ ? svn_wc_conflict_reason_moved_away
+ : svn_wc_conflict_reason_deleted),
+ action, move_src_op_root_relpath,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, conflict_root_relpath);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_add_directory(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ int op_depth = relpath_depth(b->move_root_dst_relpath);
+ const char *move_dst_repos_relpath;
+ svn_node_kind_t move_dst_kind;
+ svn_boolean_t is_conflicted;
+ const char *abspath;
+ svn_node_kind_t old_kind;
+ svn_skel_t *work_item;
+ svn_wc_notify_action_t action = svn_wc_notify_update_add;
+ svn_error_t *err;
+
+ /* Update NODES, only the bits not covered by the later call to
+ replace_moved_layer. */
+ SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_dir,
+ op_depth, scratch_pool));
+
+ err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ old_kind = svn_node_none;
+ move_dst_repos_relpath = NULL;
+ }
+ else
+ {
+ SVN_ERR(err);
+ old_kind = move_dst_kind;
+ }
+
+ /* Check for NODES tree-conflict. */
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath,
+ old_kind, svn_node_dir,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_add,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ /* Check for unversioned tree-conflict */
+ abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool);
+ SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool));
+
+ switch (old_kind)
+ {
+ case svn_node_file:
+ default:
+ SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version,
+ b->new_version, b->move_root_dst_relpath,
+ b->operation, old_kind, svn_node_dir,
+ move_dst_repos_relpath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add, NULL,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath);
+ action = svn_wc_notify_tree_conflict;
+ is_conflicted = TRUE;
+ break;
+
+ case svn_node_none:
+ SVN_ERR(svn_wc__wq_build_dir_install(&work_item, b->db, abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ scratch_pool));
+ /* Fall through */
+ case svn_node_dir:
+ break;
+ }
+
+ if (!is_conflicted)
+ SVN_ERR(update_move_list_add(b->wcroot, relpath,
+ action,
+ svn_node_dir,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_add_file(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ int op_depth = relpath_depth(b->move_root_dst_relpath);
+ const char *move_dst_repos_relpath;
+ svn_node_kind_t move_dst_kind;
+ svn_node_kind_t old_kind;
+ svn_boolean_t is_conflicted;
+ const char *abspath;
+ svn_skel_t *work_item;
+ svn_error_t *err;
+
+ /* Update NODES, only the bits not covered by the later call to
+ replace_moved_layer. */
+ SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_file,
+ op_depth, scratch_pool));
+
+ err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ old_kind = svn_node_none;
+ move_dst_repos_relpath = NULL;
+ }
+ else
+ {
+ SVN_ERR(err);
+ old_kind = move_dst_kind;
+ }
+
+ /* Check for NODES tree-conflict. */
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath,
+ old_kind, svn_node_file, move_dst_repos_relpath,
+ svn_wc_conflict_action_add,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ /* Check for unversioned tree-conflict */
+ abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool);
+ SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool));
+
+ if (old_kind != svn_node_none)
+ {
+ SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version,
+ b->new_version, b->move_root_dst_relpath,
+ b->operation, old_kind, svn_node_file,
+ move_dst_repos_relpath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add, NULL,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath);
+ return SVN_NO_ERROR;
+ }
+
+ /* Update working file. */
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, b->db,
+ svn_dirent_join(b->wcroot->abspath,
+ relpath,
+ scratch_pool),
+ NULL,
+ FALSE /* FIXME: use_commit_times? */,
+ TRUE /* record_file_info */,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ scratch_pool));
+
+ SVN_ERR(update_move_list_add(b->wcroot, relpath,
+ svn_wc_notify_update_add,
+ svn_node_file,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_add_symlink(void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_add_absent(void *baton,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+/* All the info we need about one version of a working node. */
+typedef struct working_node_version_t
+{
+ svn_wc_conflict_version_t *location_and_kind;
+ apr_hash_t *props;
+ const svn_checksum_t *checksum; /* for files only */
+} working_node_version_t;
+
+/* Return *WORK_ITEMS to create a conflict on LOCAL_ABSPATH. */
+static svn_error_t *
+create_conflict_markers(svn_skel_t **work_items,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ const char *repos_relpath,
+ svn_skel_t *conflict_skel,
+ svn_wc_operation_t operation,
+ const working_node_version_t *old_version,
+ const working_node_version_t *new_version,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_version_t *original_version;
+ svn_wc_conflict_version_t *conflicted_version;
+ const char *part;
+
+ original_version = svn_wc_conflict_version_dup(
+ old_version->location_and_kind, scratch_pool);
+ original_version->node_kind = kind;
+ conflicted_version = svn_wc_conflict_version_dup(
+ new_version->location_and_kind, scratch_pool);
+ conflicted_version->node_kind = kind;
+
+ part = svn_relpath_skip_ancestor(original_version->path_in_repos,
+ repos_relpath);
+ conflicted_version->path_in_repos
+ = svn_relpath_join(conflicted_version->path_in_repos, part, scratch_pool);
+ original_version->path_in_repos = repos_relpath;
+
+ if (operation == svn_wc_operation_update)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(
+ conflict_skel, original_version,
+ conflicted_version,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(
+ conflict_skel, original_version,
+ conflicted_version,
+ scratch_pool, scratch_pool));
+ }
+
+ /* According to this func's doc string, it is "Currently only used for
+ * property conflicts as text conflict markers are just in-wc files." */
+ SVN_ERR(svn_wc__conflict_create_markers(work_items, db,
+ local_abspath,
+ conflict_skel,
+ result_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_working_props(svn_wc_notify_state_t *prop_state,
+ svn_skel_t **conflict_skel,
+ apr_array_header_t **propchanges,
+ apr_hash_t **actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const struct working_node_version_t *old_version,
+ const struct working_node_version_t *new_version,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *new_actual_props;
+ apr_array_header_t *new_propchanges;
+
+ /*
+ * Run a 3-way prop merge to update the props, using the pre-update
+ * props as the merge base, the post-update props as the
+ * merge-left version, and the current props of the
+ * moved-here working file as the merge-right version.
+ */
+ SVN_ERR(svn_wc__db_read_props(actual_props,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_prop_diffs(propchanges, new_version->props, old_version->props,
+ result_pool));
+ SVN_ERR(svn_wc__merge_props(conflict_skel, prop_state,
+ &new_actual_props,
+ db, local_abspath,
+ old_version->props, old_version->props,
+ *actual_props, *propchanges,
+ result_pool, scratch_pool));
+
+ /* Setting properties in ACTUAL_NODE with svn_wc__db_op_set_props
+ relies on NODES row having been updated first which we don't do
+ at present. So this extra property diff has the same effect.
+
+ ### Perhaps we should update NODES first (but after
+ ### svn_wc__db_read_props above)? */
+ SVN_ERR(svn_prop_diffs(&new_propchanges, new_actual_props, new_version->props,
+ scratch_pool));
+ if (!new_propchanges->nelts)
+ new_actual_props = NULL;
+
+ /* Install the new actual props. Don't set the conflict_skel yet, because
+ we might need to add a text conflict to it as well. */
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath,
+ new_actual_props,
+ svn_wc__has_magic_property(*propchanges),
+ NULL/*conflict_skel*/, NULL/*work_items*/,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_alter_directory(void *baton,
+ const char *dst_relpath,
+ svn_revnum_t expected_move_dst_revision,
+ const apr_array_header_t *children,
+ apr_hash_t *new_props,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ const char *move_dst_repos_relpath;
+ svn_revnum_t move_dst_revision;
+ svn_node_kind_t move_dst_kind;
+ working_node_version_t old_version, new_version;
+ svn_wc__db_status_t status;
+ svn_boolean_t is_conflicted;
+
+ SVN_ERR_ASSERT(expected_move_dst_revision == b->old_version->peg_rev);
+
+ SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, &old_version.checksum, NULL,
+ NULL, &old_version.props,
+ b->wcroot, dst_relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool));
+
+ /* If the node would be recorded as svn_wc__db_status_base_deleted it
+ wouldn't have a repos_relpath */
+ /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */
+ if (status == svn_wc__db_status_deleted && move_dst_repos_relpath)
+ status = svn_wc__db_status_not_present;
+
+ /* There might be not-present nodes of a different revision as the same
+ depth as a copy. This is commonly caused by copying/moving mixed revision
+ directories */
+ SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision
+ || status == svn_wc__db_status_not_present);
+ SVN_ERR_ASSERT(move_dst_kind == svn_node_dir);
+
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath,
+ move_dst_kind,
+ svn_node_dir,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_edit,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ old_version.location_and_kind = b->old_version;
+ new_version.location_and_kind = b->new_version;
+
+ new_version.checksum = NULL; /* not a file */
+ new_version.props = new_props ? new_props : old_version.props;
+
+ if (new_props)
+ {
+ const char *dst_abspath = svn_dirent_join(b->wcroot->abspath,
+ dst_relpath,
+ scratch_pool);
+ svn_wc_notify_state_t prop_state;
+ svn_skel_t *conflict_skel = NULL;
+ apr_hash_t *actual_props;
+ apr_array_header_t *propchanges;
+
+ SVN_ERR(update_working_props(&prop_state, &conflict_skel,
+ &propchanges, &actual_props,
+ b->db, dst_abspath,
+ &old_version, &new_version,
+ scratch_pool, scratch_pool));
+
+ if (conflict_skel)
+ {
+ svn_skel_t *work_items;
+
+ SVN_ERR(create_conflict_markers(&work_items, dst_abspath,
+ b->db, move_dst_repos_relpath,
+ conflict_skel, b->operation,
+ &old_version, &new_version,
+ svn_node_dir,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_mark_conflict_internal(b->wcroot, dst_relpath,
+ conflict_skel,
+ scratch_pool));
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_items,
+ scratch_pool));
+ }
+
+ SVN_ERR(update_move_list_add(b->wcroot, dst_relpath,
+ svn_wc_notify_update_update,
+ svn_node_dir,
+ svn_wc_notify_state_inapplicable,
+ prop_state));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Merge the difference between OLD_VERSION and NEW_VERSION into
+ * the working file at LOCAL_RELPATH.
+ *
+ * The term 'old' refers to the pre-update state, which is the state of
+ * (some layer of) LOCAL_RELPATH while this function runs; and 'new'
+ * refers to the post-update state, as found at the (base layer of) the
+ * move source path while this function runs.
+ *
+ * LOCAL_RELPATH is a file in the working copy at WCROOT in DB, and
+ * REPOS_RELPATH is the repository path it would be committed to.
+ *
+ * Use NOTIFY_FUNC and NOTIFY_BATON for notifications.
+ * Set *WORK_ITEMS to any required work items, allocated in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+update_working_file(const char *local_relpath,
+ const char *repos_relpath,
+ svn_wc_operation_t operation,
+ const working_node_version_t *old_version,
+ const working_node_version_t *new_version,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath = svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool);
+ const char *old_pristine_abspath;
+ const char *new_pristine_abspath;
+ svn_skel_t *conflict_skel = NULL;
+ apr_hash_t *actual_props;
+ apr_array_header_t *propchanges;
+ enum svn_wc_merge_outcome_t merge_outcome;
+ svn_wc_notify_state_t prop_state, content_state;
+ svn_skel_t *work_item, *work_items = NULL;
+
+ SVN_ERR(update_working_props(&prop_state, &conflict_skel, &propchanges,
+ &actual_props, db, local_abspath,
+ old_version, new_version,
+ scratch_pool, scratch_pool));
+
+ if (!svn_checksum_match(new_version->checksum, old_version->checksum))
+ {
+ svn_boolean_t is_locally_modified;
+
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified,
+ db, local_abspath,
+ FALSE /* exact_comparison */,
+ scratch_pool));
+ if (!is_locally_modified)
+ {
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, db,
+ local_abspath,
+ NULL,
+ FALSE /* FIXME: use_commit_times? */,
+ TRUE /* record_file_info */,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ content_state = svn_wc_notify_state_changed;
+ }
+ else
+ {
+ /*
+ * Run a 3-way merge to update the file, using the pre-update
+ * pristine text as the merge base, the post-update pristine
+ * text as the merge-left version, and the current content of the
+ * moved-here working file as the merge-right version.
+ */
+ SVN_ERR(svn_wc__db_pristine_get_path(&old_pristine_abspath,
+ db, wcroot->abspath,
+ old_version->checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_pristine_get_path(&new_pristine_abspath,
+ db, wcroot->abspath,
+ new_version->checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel,
+ &merge_outcome, db,
+ old_pristine_abspath,
+ new_pristine_abspath,
+ local_abspath,
+ local_abspath,
+ NULL, NULL, NULL, /* diff labels */
+ actual_props,
+ FALSE, /* dry-run */
+ NULL, /* diff3-cmd */
+ NULL, /* merge options */
+ propchanges,
+ NULL, NULL, /* cancel_func + baton */
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ if (merge_outcome == svn_wc_merge_conflict)
+ content_state = svn_wc_notify_state_conflicted;
+ else
+ content_state = svn_wc_notify_state_merged;
+ }
+ }
+ else
+ content_state = svn_wc_notify_state_unchanged;
+
+ /* If there are any conflicts to be stored, convert them into work items
+ * too. */
+ if (conflict_skel)
+ {
+ SVN_ERR(create_conflict_markers(&work_item, local_abspath, db,
+ repos_relpath, conflict_skel,
+ operation, old_version, new_version,
+ svn_node_file,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict_skel,
+ scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ SVN_ERR(svn_wc__db_wq_add(db, wcroot->abspath, work_items, scratch_pool));
+
+ SVN_ERR(update_move_list_add(wcroot, local_relpath,
+ svn_wc_notify_update_update,
+ svn_node_file,
+ content_state,
+ prop_state));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Edit the file found at the move destination, which is initially at
+ * the old state. Merge the changes into the "working"/"actual" file.
+ */
+static svn_error_t *
+tc_editor_alter_file(void *baton,
+ const char *dst_relpath,
+ svn_revnum_t expected_move_dst_revision,
+ apr_hash_t *new_props,
+ const svn_checksum_t *new_checksum,
+ svn_stream_t *new_contents,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ const char *move_dst_repos_relpath;
+ svn_revnum_t move_dst_revision;
+ svn_node_kind_t move_dst_kind;
+ working_node_version_t old_version, new_version;
+ svn_boolean_t is_conflicted;
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, &old_version.checksum, NULL,
+ NULL, &old_version.props,
+ b->wcroot, dst_relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool));
+
+ /* If the node would be recorded as svn_wc__db_status_base_deleted it
+ wouldn't have a repos_relpath */
+ /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */
+ if (status == svn_wc__db_status_deleted && move_dst_repos_relpath)
+ status = svn_wc__db_status_not_present;
+
+ SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision
+ || status == svn_wc__db_status_not_present);
+ SVN_ERR_ASSERT(move_dst_kind == svn_node_file);
+
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath,
+ move_dst_kind,
+ svn_node_file,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_edit,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ old_version.location_and_kind = b->old_version;
+ new_version.location_and_kind = b->new_version;
+
+ /* If new checksum is null that means no change; similarly props. */
+ new_version.checksum = new_checksum ? new_checksum : old_version.checksum;
+ new_version.props = new_props ? new_props : old_version.props;
+
+ /* Update file and prop contents if the update has changed them. */
+ if (!svn_checksum_match(new_checksum, old_version.checksum) || new_props)
+ {
+ SVN_ERR(update_working_file(dst_relpath, move_dst_repos_relpath,
+ b->operation, &old_version, &new_version,
+ b->wcroot, b->db,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_alter_symlink(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_delete(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ svn_sqlite__stmt_t *stmt;
+ int op_depth = relpath_depth(b->move_root_dst_relpath);
+ const char *move_dst_repos_relpath;
+ svn_node_kind_t move_dst_kind;
+ svn_boolean_t is_conflicted;
+ svn_boolean_t must_delete_working_nodes = FALSE;
+ const char *local_abspath = svn_dirent_join(b->wcroot->abspath, relpath,
+ scratch_pool);
+ const char *parent_relpath = svn_relpath_dirname(relpath, scratch_pool);
+ int op_depth_below;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool));
+
+ /* Check before retracting delete to catch delete-delete
+ conflicts. This catches conflicts on the node itself; deleted
+ children are caught as local modifications below.*/
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath,
+ move_dst_kind,
+ svn_node_unknown,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_delete,
+ scratch_pool));
+
+ if (!is_conflicted)
+ {
+ svn_boolean_t is_modified, is_all_deletes;
+
+ SVN_ERR(svn_wc__node_has_local_mods(&is_modified, &is_all_deletes, b->db,
+ local_abspath,
+ NULL, NULL, scratch_pool));
+ if (is_modified)
+ {
+ svn_wc_conflict_reason_t reason;
+
+ if (!is_all_deletes)
+ {
+ /* No conflict means no NODES rows at the relpath op-depth
+ so it's easy to convert the modified tree into a copy. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_UPDATE_OP_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath,
+ op_depth, relpath_depth(relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ reason = svn_wc_conflict_reason_edited;
+ }
+ else
+ {
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_DELETE_WORKING_OP_DEPTH_ABOVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ reason = svn_wc_conflict_reason_deleted;
+ must_delete_working_nodes = TRUE;
+ }
+ is_conflicted = TRUE;
+ SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version,
+ b->new_version, b->move_root_dst_relpath,
+ b->operation,
+ move_dst_kind,
+ svn_node_none,
+ move_dst_repos_relpath, reason,
+ svn_wc_conflict_action_delete, NULL,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath);
+ }
+ }
+
+ if (!is_conflicted || must_delete_working_nodes)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_skel_t *work_item;
+ svn_node_kind_t del_kind;
+ const char *del_abspath;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_SELECT_CHILDREN_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ del_kind = svn_sqlite__column_token(stmt, 1, kind_map);
+ del_abspath = svn_dirent_join(b->wcroot->abspath,
+ svn_sqlite__column_text(stmt, 0, NULL),
+ iterpool);
+ if (del_kind == svn_node_dir)
+ err = svn_wc__wq_build_dir_remove(&work_item, b->db,
+ b->wcroot->abspath, del_abspath,
+ FALSE /* recursive */,
+ iterpool, iterpool);
+ else
+ err = svn_wc__wq_build_file_remove(&work_item, b->db,
+ b->wcroot->abspath, del_abspath,
+ iterpool, iterpool);
+ if (!err)
+ err = svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &del_kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ b->wcroot, relpath, op_depth,
+ iterpool, iterpool));
+ if (del_kind == svn_node_dir)
+ SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, b->db,
+ b->wcroot->abspath, local_abspath,
+ FALSE /* recursive */,
+ iterpool, iterpool));
+ else
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, b->db,
+ b->wcroot->abspath, local_abspath,
+ iterpool, iterpool));
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ iterpool));
+
+ if (!is_conflicted)
+ SVN_ERR(update_move_list_add(b->wcroot, relpath,
+ svn_wc_notify_update_delete,
+ del_kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ svn_pool_destroy(iterpool);
+ }
+
+ /* Deleting the ROWS is valid so long as we update the parent before
+ committing the transaction. The removed rows could have been
+ replacing a lower layer in which case we need to add base-deleted
+ rows. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_SELECT_HIGHEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, parent_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ op_depth_below = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ {
+ /* Remove non-shadowing nodes. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_DELETE_NO_LOWER_LAYER));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath,
+ op_depth, op_depth_below));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Convert remaining shadowing nodes to presence='base-deleted'. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_REPLACE_WITH_BASE_DELETED));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_DELETE_WORKING_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Retract any base-delete. */
+ SVN_ERR(svn_wc__db_retract_parent_delete(b->wcroot, relpath, op_depth,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_copy(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_move(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_rotate(void *baton,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_complete(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_abort(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* The editor callback table implementing the receiver. */
+static const svn_editor_cb_many_t editor_ops = {
+ tc_editor_add_directory,
+ tc_editor_add_file,
+ tc_editor_add_symlink,
+ tc_editor_add_absent,
+ tc_editor_alter_directory,
+ tc_editor_alter_file,
+ tc_editor_alter_symlink,
+ tc_editor_delete,
+ tc_editor_copy,
+ tc_editor_move,
+ tc_editor_rotate,
+ tc_editor_complete,
+ tc_editor_abort
+};
+
+
+/*
+ * Driver code.
+ *
+ * The scenario is that a subtree has been locally moved, and then the base
+ * layer on the source side of the move has received an update to a new
+ * state. The destination subtree has not yet been updated, and still
+ * matches the pre-update state of the source subtree.
+ *
+ * The edit driver drives the receiver with the difference between the
+ * pre-update state (as found now at the move-destination) and the
+ * post-update state (found now at the move-source).
+ *
+ * We currently assume that both the pre-update and post-update states are
+ * single-revision.
+ */
+
+/* Set *OPERATION, *LOCAL_CHANGE, *INCOMING_CHANGE, *OLD_VERSION, *NEW_VERSION
+ * to reflect the tree conflict on the victim SRC_ABSPATH in DB.
+ *
+ * If SRC_ABSPATH is not a tree-conflict victim, return an error.
+ */
+static svn_error_t *
+get_tc_info(svn_wc_operation_t *operation,
+ svn_wc_conflict_reason_t *local_change,
+ svn_wc_conflict_action_t *incoming_change,
+ const char **move_src_op_root_abspath,
+ svn_wc_conflict_version_t **old_version,
+ svn_wc_conflict_version_t **new_version,
+ svn_wc__db_t *db,
+ const char *src_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *locations;
+ svn_boolean_t tree_conflicted;
+ svn_skel_t *conflict_skel;
+
+ /* Check for tree conflict on src. */
+ SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db,
+ src_abspath,
+ scratch_pool, scratch_pool));
+ if (!conflict_skel)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not in conflict"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_info(operation, &locations,
+ NULL, NULL, &tree_conflicted,
+ db, src_abspath,
+ conflict_skel, result_pool,
+ scratch_pool));
+ if ((*operation != svn_wc_operation_update
+ && *operation != svn_wc_operation_switch)
+ || !tree_conflicted)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not a tree-conflict victim"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+ if (locations)
+ {
+ SVN_ERR_ASSERT(locations->nelts >= 2);
+ *old_version = APR_ARRAY_IDX(locations, 0,
+ svn_wc_conflict_version_t *);
+ *new_version = APR_ARRAY_IDX(locations, 1,
+ svn_wc_conflict_version_t *);
+ }
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(local_change,
+ incoming_change,
+ move_src_op_root_abspath,
+ db, src_abspath,
+ conflict_skel, scratch_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Return *PROPS, *CHECKSUM, *CHILDREN and *KIND for LOCAL_RELPATH at
+ OP_DEPTH provided the row exists. Return *KIND of svn_node_none if
+ the row does not exist. *CHILDREN is a sorted array of basenames of
+ type 'const char *', rather than a hash, to allow the driver to
+ process children in a defined order. */
+static svn_error_t *
+get_info(apr_hash_t **props,
+ const svn_checksum_t **checksum,
+ apr_array_header_t **children,
+ svn_node_kind_t *kind,
+ const char *local_relpath,
+ int op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *hash_children;
+ apr_array_header_t *sorted_children;
+ svn_error_t *err;
+ int i;
+
+ err = svn_wc__db_depth_get_info(NULL, kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, checksum, NULL, NULL, props,
+ wcroot, local_relpath, op_depth,
+ result_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *kind = svn_node_none;
+ }
+ else
+ SVN_ERR(err);
+
+
+ SVN_ERR(svn_wc__db_get_children_op_depth(&hash_children, wcroot,
+ local_relpath, op_depth,
+ scratch_pool, scratch_pool));
+
+ sorted_children = svn_sort__hash(hash_children,
+ svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ *children = apr_array_make(result_pool, sorted_children->nelts,
+ sizeof(const char *));
+ for (i = 0; i < sorted_children->nelts; ++i)
+ APR_ARRAY_PUSH(*children, const char *)
+ = apr_pstrdup(result_pool, APR_ARRAY_IDX(sorted_children, i,
+ svn_sort__item_t).key);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE if SRC_CHILDREN and DST_CHILDREN represent the same
+ children, FALSE otherwise. SRC_CHILDREN and DST_CHILDREN are
+ sorted arrays of basenames of type 'const char *'. */
+static svn_boolean_t
+children_match(apr_array_header_t *src_children,
+ apr_array_header_t *dst_children) { int i;
+
+ if (src_children->nelts != dst_children->nelts)
+ return FALSE;
+
+ for(i = 0; i < src_children->nelts; ++i)
+ {
+ const char *src_child =
+ APR_ARRAY_IDX(src_children, i, const char *);
+ const char *dst_child =
+ APR_ARRAY_IDX(dst_children, i, const char *);
+
+ if (strcmp(src_child, dst_child))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Return TRUE if SRC_PROPS and DST_PROPS contain the same properties,
+ FALSE otherwise. SRC_PROPS and DST_PROPS are standard property
+ hashes. */
+static svn_error_t *
+props_match(svn_boolean_t *match,
+ apr_hash_t *src_props,
+ apr_hash_t *dst_props,
+ apr_pool_t *scratch_pool)
+{
+ if (!src_props && !dst_props)
+ *match = TRUE;
+ else if (!src_props || ! dst_props)
+ *match = FALSE;
+ else
+ {
+ apr_array_header_t *propdiffs;
+
+ SVN_ERR(svn_prop_diffs(&propdiffs, src_props, dst_props, scratch_pool));
+ *match = propdiffs->nelts ? FALSE : TRUE;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* ### Drive TC_EDITOR so as to ...
+ */
+static svn_error_t *
+update_moved_away_node(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ int src_op_depth,
+ const char *move_root_dst_relpath,
+ svn_revnum_t move_root_dst_revision,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t src_kind, dst_kind;
+ const svn_checksum_t *src_checksum, *dst_checksum;
+ apr_hash_t *src_props, *dst_props;
+ apr_array_header_t *src_children, *dst_children;
+ int dst_op_depth = relpath_depth(move_root_dst_relpath);
+
+ SVN_ERR(get_info(&src_props, &src_checksum, &src_children, &src_kind,
+ src_relpath, src_op_depth,
+ wcroot, scratch_pool, scratch_pool));
+
+ SVN_ERR(get_info(&dst_props, &dst_checksum, &dst_children, &dst_kind,
+ dst_relpath, dst_op_depth,
+ wcroot, scratch_pool, scratch_pool));
+
+ if (src_kind == svn_node_none
+ || (dst_kind != svn_node_none && src_kind != dst_kind))
+ {
+ SVN_ERR(svn_editor_delete(tc_editor, dst_relpath,
+ move_root_dst_revision));
+ }
+
+ if (src_kind != svn_node_none && src_kind != dst_kind)
+ {
+ if (src_kind == svn_node_file || src_kind == svn_node_symlink)
+ {
+ svn_stream_t *contents;
+
+ SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db,
+ wcroot->abspath, src_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_editor_add_file(tc_editor, dst_relpath,
+ src_checksum, contents, src_props,
+ move_root_dst_revision));
+ }
+ else if (src_kind == svn_node_dir)
+ {
+ SVN_ERR(svn_editor_add_directory(tc_editor, dst_relpath,
+ src_children, src_props,
+ move_root_dst_revision));
+ }
+ }
+ else if (src_kind != svn_node_none)
+ {
+ svn_boolean_t match;
+ apr_hash_t *props;
+
+ SVN_ERR(props_match(&match, src_props, dst_props, scratch_pool));
+ props = match ? NULL: src_props;
+
+
+ if (src_kind == svn_node_file || src_kind == svn_node_symlink)
+ {
+ svn_stream_t *contents;
+
+ if (svn_checksum_match(src_checksum, dst_checksum))
+ src_checksum = NULL;
+
+ if (src_checksum)
+ SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db,
+ wcroot->abspath, src_checksum,
+ scratch_pool, scratch_pool));
+ else
+ contents = NULL;
+
+ if (props || src_checksum)
+ SVN_ERR(svn_editor_alter_file(tc_editor, dst_relpath,
+ move_root_dst_revision,
+ props, src_checksum, contents));
+ }
+ else if (src_kind == svn_node_dir)
+ {
+ apr_array_header_t *children
+ = children_match(src_children, dst_children) ? NULL : src_children;
+
+ if (props || children)
+ SVN_ERR(svn_editor_alter_directory(tc_editor, dst_relpath,
+ move_root_dst_revision,
+ children, props));
+ }
+ }
+
+ if (src_kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i = 0, j = 0;
+
+ while (i < src_children->nelts || j < dst_children->nelts)
+ {
+ const char *child_name;
+ const char *src_child_relpath, *dst_child_relpath;
+ svn_boolean_t src_only = FALSE, dst_only = FALSE;
+
+ svn_pool_clear(iterpool);
+ if (i >= src_children->nelts)
+ {
+ dst_only = TRUE;
+ child_name = APR_ARRAY_IDX(dst_children, j, const char *);
+ }
+ else if (j >= dst_children->nelts)
+ {
+ src_only = TRUE;
+ child_name = APR_ARRAY_IDX(src_children, i, const char *);
+ }
+ else
+ {
+ const char *src_name = APR_ARRAY_IDX(src_children, i,
+ const char *);
+ const char *dst_name = APR_ARRAY_IDX(dst_children, j,
+ const char *);
+ int cmp = strcmp(src_name, dst_name);
+
+ if (cmp > 0)
+ dst_only = TRUE;
+ else if (cmp < 0)
+ src_only = TRUE;
+
+ child_name = dst_only ? dst_name : src_name;
+ }
+
+ src_child_relpath = svn_relpath_join(src_relpath, child_name,
+ iterpool);
+ dst_child_relpath = svn_relpath_join(dst_relpath, child_name,
+ iterpool);
+
+ SVN_ERR(update_moved_away_node(tc_editor, src_child_relpath,
+ dst_child_relpath, src_op_depth,
+ move_root_dst_relpath,
+ move_root_dst_revision,
+ db, wcroot, scratch_pool));
+
+ if (!dst_only)
+ ++i;
+ if (!src_only)
+ ++j;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Update the single op-depth layer in the move destination subtree
+ rooted at DST_RELPATH to make it match the move source subtree
+ rooted at SRC_RELPATH. */
+static svn_error_t *
+replace_moved_layer(const char *src_relpath,
+ const char *dst_relpath,
+ int src_op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int dst_op_depth = relpath_depth(dst_relpath);
+
+ /* Replace entire subtree at one op-depth. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_LOCAL_RELPATH_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ src_relpath, src_op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_error_t *err;
+ svn_sqlite__stmt_t *stmt2;
+ const char *src_cp_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *dst_cp_relpath
+ = svn_relpath_join(dst_relpath,
+ svn_relpath_skip_ancestor(src_relpath,
+ src_cp_relpath),
+ scratch_pool);
+
+ err = svn_sqlite__get_statement(&stmt2, wcroot->sdb,
+ STMT_COPY_NODE_MOVE);
+ if (!err)
+ err = svn_sqlite__bindf(stmt2, "isdsds", wcroot->wc_id,
+ src_cp_relpath, src_op_depth,
+ dst_cp_relpath, dst_op_depth,
+ svn_relpath_dirname(dst_cp_relpath,
+ scratch_pool));
+ if (!err)
+ err = svn_sqlite__step_done(stmt2);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Transfer changes from the move source to the move destination.
+ *
+ * Drive the editor TC_EDITOR with the difference between DST_RELPATH
+ * (at its own op-depth) and SRC_RELPATH (at op-depth zero).
+ *
+ * Then update the single op-depth layer in the move destination subtree
+ * rooted at DST_RELPATH to make it match the move source subtree
+ * rooted at SRC_RELPATH.
+ *
+ * ### And the other params?
+ */
+static svn_error_t *
+drive_tree_conflict_editor(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ int src_op_depth,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ /*
+ * Refuse to auto-resolve unsupported tree conflicts.
+ */
+ /* ### Only handle conflicts created by update/switch operations for now. */
+ if (operation != svn_wc_operation_update &&
+ operation != svn_wc_operation_switch)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot auto-resolve tree-conflict on '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath,
+ src_relpath, scratch_pool),
+ scratch_pool));
+
+ /* We walk the move source (i.e. the post-update tree), comparing each node
+ * with the equivalent node at the move destination and applying the update
+ * to nodes at the move destination. */
+ SVN_ERR(update_moved_away_node(tc_editor, src_relpath, dst_relpath,
+ src_op_depth,
+ dst_relpath, old_version->peg_rev,
+ db, wcroot, scratch_pool));
+
+ SVN_ERR(replace_moved_layer(src_relpath, dst_relpath, src_op_depth,
+ wcroot, scratch_pool));
+
+ SVN_ERR(svn_editor_complete(tc_editor));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+suitable_for_move(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_revnum_t revision;
+ const char *repos_relpath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ revision = svn_sqlite__column_revnum(stmt, 4);
+ repos_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool);
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!have_row)
+ return SVN_NO_ERROR; /* Return an error? */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REPOS_PATH_REVISION));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_revnum_t node_revision = svn_sqlite__column_revnum(stmt, 2);
+ const char *relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ svn_pool_clear(iterpool);
+
+ relpath = svn_relpath_skip_ancestor(local_relpath, relpath);
+ relpath = svn_relpath_join(repos_relpath, relpath, iterpool);
+
+ if (revision != node_revision)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ svn_sqlite__reset(stmt),
+ _("Cannot apply update because move source "
+ "%s' is a mixed-revision working copy"),
+ svn_dirent_local_style(svn_dirent_join(
+ wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ if (strcmp(relpath, svn_sqlite__column_text(stmt, 1, NULL)))
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ svn_sqlite__reset(stmt),
+ _("Cannot apply update because move source "
+ "'%s' is a switched subtree"),
+ svn_dirent_local_style(svn_dirent_join(
+ wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_update_moved_away_conflict_victim(), which see.
+ */
+static svn_error_t *
+update_moved_away_conflict_victim(svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *victim_relpath,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ const char *move_src_op_root_relpath,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor_t *tc_editor;
+ struct tc_editor_baton *tc_editor_baton;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *dummy1, *dummy2, *dummy3;
+ int src_op_depth;
+ const char *move_root_dst_abspath;
+
+ /* ### assumes wc write lock already held */
+
+ /* Construct editor baton. */
+ tc_editor_baton = apr_pcalloc(scratch_pool, sizeof(*tc_editor_baton));
+ SVN_ERR(svn_wc__db_op_depth_moved_to(
+ &dummy1, &tc_editor_baton->move_root_dst_relpath, &dummy2, &dummy3,
+ relpath_depth(move_src_op_root_relpath) - 1,
+ wcroot, victim_relpath, scratch_pool, scratch_pool));
+ if (tc_editor_baton->move_root_dst_relpath == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("The node '%s' has not been moved away"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath, victim_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ move_root_dst_abspath
+ = svn_dirent_join(wcroot->abspath, tc_editor_baton->move_root_dst_relpath,
+ scratch_pool);
+ SVN_ERR(svn_wc__write_check(db, move_root_dst_abspath, scratch_pool));
+
+ tc_editor_baton->operation = operation;
+ tc_editor_baton->old_version= old_version;
+ tc_editor_baton->new_version= new_version;
+ tc_editor_baton->db = db;
+ tc_editor_baton->wcroot = wcroot;
+ tc_editor_baton->result_pool = scratch_pool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_HIGHEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ move_src_op_root_relpath,
+ relpath_depth(move_src_op_root_relpath)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ src_op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not deleted"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath, victim_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ if (src_op_depth == 0)
+ SVN_ERR(suitable_for_move(wcroot, victim_relpath, scratch_pool));
+
+ /* Create a new, and empty, list for notification information. */
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+ /* Create the editor... */
+ SVN_ERR(svn_editor_create(&tc_editor, tc_editor_baton,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_many(tc_editor, &editor_ops, scratch_pool));
+
+ /* ... and drive it. */
+ SVN_ERR(drive_tree_conflict_editor(tc_editor,
+ victim_relpath,
+ tc_editor_baton->move_root_dst_relpath,
+ src_op_depth,
+ operation,
+ local_change, incoming_change,
+ tc_editor_baton->old_version,
+ tc_editor_baton->new_version,
+ db, wcroot,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db,
+ const char *victim_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_reason_t local_change;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+ const char *move_src_op_root_abspath, *move_src_op_root_relpath;
+
+ /* ### Check for mixed-rev src or dst? */
+
+ SVN_ERR(get_tc_info(&operation, &local_change, &incoming_change,
+ &move_src_op_root_abspath,
+ &old_version, &new_version,
+ db, victim_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__write_check(db, move_src_op_root_abspath, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, victim_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ move_src_op_root_relpath
+ = svn_dirent_skip_ancestor(wcroot->abspath, move_src_op_root_abspath);
+
+ SVN_WC__DB_WITH_TXN(
+ update_moved_away_conflict_victim(
+ db, wcroot, local_relpath,
+ operation, local_change, incoming_change,
+ move_src_op_root_relpath,
+ old_version, new_version,
+ cancel_func, cancel_baton,
+ scratch_pool),
+ wcroot);
+
+ /* Send all queued up notifications. */
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot,
+ (old_version
+ ? old_version->peg_rev
+ : SVN_INVALID_REVNUM),
+ (new_version
+ ? new_version->peg_rev
+ : SVN_INVALID_REVNUM),
+ notify_func, notify_baton,
+ scratch_pool));
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ svn_wc_notify_update_completed,
+ scratch_pool);
+ notify->kind = svn_node_none;
+ notify->content_state = svn_wc_notify_state_inapplicable;
+ notify->prop_state = svn_wc_notify_state_inapplicable;
+ notify->revision = new_version->peg_rev;
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *CAN_BUMP to TRUE if DEPTH is sufficient to cover the entire
+ BASE tree at LOCAL_RELPATH, to FALSE otherwise. */
+static svn_error_t *
+depth_sufficient_to_bump(svn_boolean_t *can_bump,
+ const char *local_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ switch (depth)
+ {
+ case svn_depth_infinity:
+ *can_bump = TRUE;
+ return SVN_NO_ERROR;
+
+ case svn_depth_empty:
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath, 0));
+ break;
+
+ case svn_depth_files:
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_HAS_NON_FILE_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ break;
+
+ case svn_depth_immediates:
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_HAS_GRANDCHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *can_bump = !have_row;
+ return SVN_NO_ERROR;
+}
+
+/* Mark a move-edit conflict on MOVE_SRC_ROOT_RELPATH. */
+static svn_error_t *
+bump_mark_tree_conflict(svn_wc__db_wcroot_t *wcroot,
+ const char *move_src_root_relpath,
+ const char *move_src_op_root_relpath,
+ const char *move_dst_op_root_relpath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ apr_int64_t repos_id;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *old_repos_relpath;
+ const char *new_repos_relpath;
+ svn_revnum_t old_rev;
+ svn_revnum_t new_rev;
+ svn_node_kind_t old_kind;
+ svn_node_kind_t new_kind;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+
+ /* Read new (post-update) information from the new move source BASE node. */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &new_kind, &new_rev,
+ &new_repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, move_src_op_root_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid,
+ wcroot->sdb, repos_id, scratch_pool));
+
+ /* Read old (pre-update) information from the move destination node. */
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &old_kind, &old_rev,
+ &old_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, move_dst_op_root_relpath,
+ relpath_depth(move_dst_op_root_relpath),
+ scratch_pool, scratch_pool));
+
+ old_version = svn_wc_conflict_version_create2(
+ repos_root_url, repos_uuid, old_repos_relpath, old_rev,
+ old_kind, scratch_pool);
+ new_version = svn_wc_conflict_version_create2(
+ repos_root_url, repos_uuid, new_repos_relpath, new_rev,
+ new_kind, scratch_pool);
+
+ SVN_ERR(mark_tree_conflict(move_src_root_relpath,
+ wcroot, db, old_version, new_version,
+ move_dst_op_root_relpath,
+ svn_wc_operation_update,
+ old_kind, new_kind,
+ old_repos_relpath,
+ svn_wc_conflict_reason_moved_away,
+ svn_wc_conflict_action_edit,
+ move_src_op_root_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Bump LOCAL_RELPATH, and all the children of LOCAL_RELPATH, that are
+ moved-to at op-depth greater than OP_DEPTH. SRC_DONE is a hash
+ with keys that are 'const char *' relpaths that have already been
+ bumped. Any bumped paths are added to SRC_DONE. */
+static svn_error_t *
+bump_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_hash_t *src_done,
+ svn_depth_t depth,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_PAIR3));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while(have_row)
+ {
+ svn_sqlite__stmt_t *stmt2;
+ const char *src_relpath, *dst_relpath;
+ int src_op_depth = svn_sqlite__column_int(stmt, 2);
+ svn_error_t *err;
+ svn_skel_t *conflict;
+ svn_depth_t src_depth = depth;
+
+ svn_pool_clear(iterpool);
+
+ src_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+
+ if (depth != svn_depth_infinity)
+ {
+ svn_boolean_t skip_this_src = FALSE;
+ svn_node_kind_t src_kind;
+
+ if (strcmp(src_relpath, local_relpath))
+ {
+ switch (depth)
+ {
+ case svn_depth_empty:
+ skip_this_src = TRUE;
+ break;
+ case svn_depth_files:
+ src_kind = svn_sqlite__column_token(stmt, 3, kind_map);
+ if (src_kind != svn_node_file)
+ {
+ skip_this_src = TRUE;
+ break;
+ }
+ /* Fallthrough */
+ case svn_depth_immediates:
+ if (strcmp(svn_relpath_dirname(src_relpath, scratch_pool),
+ local_relpath))
+ skip_this_src = TRUE;
+ src_depth = svn_depth_empty;
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+
+ if (skip_this_src)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ continue;
+ }
+ }
+
+ err = svn_sqlite__get_statement(&stmt2, wcroot->sdb,
+ STMT_HAS_LAYER_BETWEEN);
+ if (!err)
+ err = svn_sqlite__bindf(stmt2, "isdd", wcroot->wc_id, local_relpath,
+ op_depth, src_op_depth);
+ if (!err)
+ err = svn_sqlite__step(&have_row, stmt2);
+ if (!err)
+ err = svn_sqlite__reset(stmt2);
+ if (!err && !have_row)
+ {
+ svn_boolean_t can_bump;
+ const char *src_root_relpath = src_relpath;
+
+ if (op_depth == 0)
+ err = depth_sufficient_to_bump(&can_bump, src_relpath, wcroot,
+ src_depth, scratch_pool);
+ else
+ /* Having chosen to bump an entire BASE tree move we
+ always have sufficient depth to bump subtree moves. */
+ can_bump = TRUE;
+
+ if (!err)
+ {
+ if (!can_bump)
+ {
+ err = bump_mark_tree_conflict(wcroot, src_relpath,
+ src_root_relpath, dst_relpath,
+ db, scratch_pool);
+ if (err)
+ return svn_error_compose_create(err,
+ svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ continue;
+ }
+
+ while (relpath_depth(src_root_relpath) > src_op_depth)
+ src_root_relpath = svn_relpath_dirname(src_root_relpath,
+ iterpool);
+
+ if (!svn_hash_gets(src_done, src_relpath))
+ {
+ svn_hash_sets(src_done,
+ apr_pstrdup(result_pool, src_relpath), "");
+ err = svn_wc__db_read_conflict_internal(&conflict, wcroot,
+ src_root_relpath,
+ iterpool, iterpool);
+ /* ### TODO: check this is the right sort of tree-conflict? */
+ if (!err && !conflict)
+ {
+ /* ### TODO: verify moved_here? */
+ err = replace_moved_layer(src_relpath, dst_relpath,
+ op_depth, wcroot, iterpool);
+
+ if (!err)
+ err = bump_moved_away(wcroot, dst_relpath,
+ relpath_depth(dst_relpath),
+ src_done, depth, db,
+ result_pool, iterpool);
+ }
+ }
+ }
+ }
+
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_bump_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *src_done;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ if (local_relpath[0] != '\0')
+ {
+ const char *dummy1, *move_dst_op_root_relpath;
+ const char *move_src_root_relpath, *move_src_op_root_relpath;
+
+ /* Is the root of the update moved away? (Impossible for the wcroot) */
+ SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath,
+ &move_src_root_relpath,
+ &move_src_op_root_relpath, 0,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (move_src_root_relpath)
+ {
+ if (strcmp(move_src_root_relpath, local_relpath))
+ {
+ SVN_ERR(bump_mark_tree_conflict(wcroot, move_src_root_relpath,
+ move_src_op_root_relpath,
+ move_dst_op_root_relpath,
+ db, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ src_done = apr_hash_make(scratch_pool);
+ SVN_ERR(bump_moved_away(wcroot, local_relpath, 0, src_done, depth, db,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+resolve_delete_raise_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_action_t action,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int op_depth = relpath_depth(local_relpath);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_MOVED_PAIR));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while(have_row)
+ {
+ const char *moved_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *move_root_dst_relpath = svn_sqlite__column_text(stmt, 1,
+ NULL);
+ const char *moved_dst_repos_relpath = svn_sqlite__column_text(stmt, 2,
+ NULL);
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(mark_tree_conflict(moved_relpath,
+ wcroot, db, old_version, new_version,
+ move_root_dst_relpath, operation,
+ svn_node_dir /* ### ? */,
+ svn_node_dir /* ### ? */,
+ moved_dst_repos_relpath,
+ svn_wc_conflict_reason_moved_away,
+ action, local_relpath,
+ iterpool));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_delete_raise_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_reason_t reason;
+ svn_wc_conflict_action_t action;
+ svn_wc_conflict_version_t *old_version, *new_version;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(get_tc_info(&operation, &reason, &action, NULL,
+ &old_version, &new_version,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ SVN_WC__DB_WITH_TXN(
+ resolve_delete_raise_moved_away(wcroot, local_relpath,
+ db, operation, action,
+ old_version, new_version,
+ scratch_pool),
+ wcroot);
+
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot,
+ (old_version
+ ? old_version->peg_rev
+ : SVN_INVALID_REVNUM),
+ (new_version
+ ? new_version->peg_rev
+ : SVN_INVALID_REVNUM),
+ notify_func, notify_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+break_move(svn_wc__db_wcroot_t *wcroot,
+ const char *src_relpath,
+ int src_op_depth,
+ const char *dst_relpath,
+ int dst_op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_TO_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, src_relpath,
+ src_op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* This statement clears moved_here. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_OP_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id,
+ dst_relpath, dst_op_depth, dst_op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *scratch_pool)
+{
+ const char *dummy1, *move_dst_op_root_relpath;
+ const char *dummy2, *move_src_op_root_relpath;
+
+ /* We want to include the passed op-depth, but the function does a > check */
+ SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath,
+ &dummy2,
+ &move_src_op_root_relpath,
+ op_depth - 1,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(move_src_op_root_relpath != NULL
+ && move_dst_op_root_relpath != NULL);
+
+ SVN_ERR(break_move(wcroot, local_relpath,
+ relpath_depth(move_src_op_root_relpath),
+ move_dst_op_root_relpath,
+ relpath_depth(move_dst_op_root_relpath),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+break_moved_away_children_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_PAIR2));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *src_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ const char *dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+ int src_op_depth = svn_sqlite__column_int(stmt, 2);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(break_move(wcroot, src_relpath, src_op_depth, dst_relpath,
+ relpath_depth(dst_relpath), iterpool));
+ SVN_ERR(update_move_list_add(wcroot, src_relpath,
+ svn_wc_notify_move_broken,
+ svn_node_unknown,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ svn_wc__db_resolve_break_moved_away_internal(wcroot, local_relpath,
+ relpath_depth(local_relpath),
+ scratch_pool),
+ wcroot);
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ svn_wc_notify_move_broken,
+ scratch_pool);
+ notify->kind = svn_node_unknown;
+ notify->content_state = svn_wc_notify_state_inapplicable;
+ notify->prop_state = svn_wc_notify_state_inapplicable;
+ notify->revision = SVN_INVALID_REVNUM;
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ break_moved_away_children_internal(wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ notify_func, notify_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+required_lock_for_resolve(const char **required_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *required_relpath = local_relpath;
+
+ /* This simply looks for all moves out of the LOCAL_RELPATH tree. We
+ could attempt to limit it to only those moves that are going to
+ be resolved but that would require second guessing the resolver.
+ This simple algorithm is sufficient although it may give a
+ strictly larger/deeper lock than necessary. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_OUTSIDE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *move_dst_relpath = svn_sqlite__column_text(stmt, 1,
+ NULL);
+
+ *required_relpath
+ = svn_relpath_get_longest_ancestor(*required_relpath,
+ move_dst_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *required_relpath = apr_pstrdup(result_pool, *required_relpath);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__required_lock_for_resolve(const char **required_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *required_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ required_lock_for_resolve(&required_relpath, wcroot, local_relpath,
+ scratch_pool, scratch_pool),
+ wcroot);
+
+ *required_abspath = svn_dirent_join(wcroot->abspath, required_relpath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}