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.c1837
1 files changed, 1810 insertions, 27 deletions
diff --git a/subversion/libsvn_wc/wc_db_update_move.c b/subversion/libsvn_wc/wc_db_update_move.c
index 46cbeae361c8e..ba7117581ec12 100644
--- a/subversion/libsvn_wc/wc_db_update_move.c
+++ b/subversion/libsvn_wc/wc_db_update_move.c
@@ -21,13 +21,16 @@
* ====================================================================
*/
-/* 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).
+/* This implements editors and edit drivers which are used to resolve
+ * "incoming edit, local move-away", "incoming move, local edit", and
+ * "incoming add, local add" tree conflicts 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.
+ * Our goal is to be able to resolve conflicts such that the end result
+ * is just the same as if the user had run the update *before* the local
+ * (or incoming) move or local add.
+ *
+ * -- Updating local moves --
*
* When an update (or switch) produces incoming changes for a locally
* moved-away subtree, it updates the base nodes of the moved-away tree
@@ -72,6 +75,58 @@
* 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.
+ *
+ * -- Updating incoming moves --
+ *
+ * When an update (or switch) produces an incoming move, it deletes the
+ * moved node at the old location from the BASE tree and adds a node at
+ * the new location to the BASE tree. If the old location contains local
+ * changes, a tree conflict is raised, and the former BASE tree which
+ * the local changes were based on (the tree conflict victim) is re-added
+ * as a copy which contains these local changes.
+ *
+ * The driver sees two NODES trees: The op-root of the copy, and the
+ * WORKING layer on top of this copy which represents the local changes.
+ * The driver will compare the two NODES trees and drive an editor to
+ * change the move destination's WORKING tree so that it now contains
+ * the local changes seen in the copy of the victim's tree.
+ *
+ * We require that no local changes exist at the destination, in order
+ * to avoid tree conflicts where the "incoming" and "local" change both
+ * originated in the working copy, because the resolver code cannot handle
+ * such tree conflicts at present.
+ *
+ * The whole drive occurs as one single wc.db transaction. At the end
+ * of the transaction the destination NODES table should have a WORKING
+ * layer that is equivalent to the WORKING layer found in the copied victim
+ * tree, and 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.
+ *
+ * -- Updating local adds --
+ *
+ * When an update (or switch) adds a directory tree it creates corresponding
+ * nodes in the BASE tree. Any existing locally added nodes are bumped to a
+ * higher layer with the top-most locally added directory as op-root.
+ * In-between, the update inserts a base-deleted layer, i.e. it schedules the
+ * directory in the BASE tree for removal upon the next commit, to be replaced
+ * by the locally added directory.
+ *
+ * The driver sees two NODES trees: The BASE layer, and the WORKING layer
+ * which represents the locally added tree.
+ * The driver will compare the two NODES trees and drive an editor to
+ * merge WORKING tree nodes with the nodes in the BASE tree.
+ *
+ * The whole drive occurs as one single wc.db transaction.
+ * Directories which exist in both trees become part of the BASE tree, with
+ * properties merged.
+ * Files which exist in both trees are merged (there is no common ancestor,
+ * so the common ancestor in this merge is the empty file).
+ * Files and directories which exist only in the WORKING layer become
+ * local-add op-roots of their own.
+ * Mismatching node kinds produce new 'incoming add vs local add upon update'
+ * tree conflicts which must be resolved individually later on.
*/
#define SVN_WC__I_AM_WC_DB
@@ -357,7 +412,7 @@ create_tree_conflict(svn_skel_t **conflict_p,
move_src_op_root_relpath, scratch_pool)
: NULL;
const char *old_repos_relpath_part
- = old_repos_relpath
+ = old_repos_relpath && old_version
? svn_relpath_skip_ancestor(old_version->path_in_repos,
old_repos_relpath)
: NULL;
@@ -400,7 +455,7 @@ create_tree_conflict(svn_skel_t **conflict_p,
if (conflict_operation != svn_wc_operation_update
&& conflict_operation != svn_wc_operation_switch)
- return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
_("'%s' already in conflict"),
path_for_error_message(wcroot, local_relpath,
scratch_pool));
@@ -424,7 +479,7 @@ create_tree_conflict(svn_skel_t **conflict_p,
&& strcmp(move_src_op_root_relpath,
svn_dirent_skip_ancestor(wcroot->abspath,
existing_abspath))))
- return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
_("'%s' already in conflict"),
path_for_error_message(wcroot,
local_relpath,
@@ -448,10 +503,14 @@ create_tree_conflict(svn_skel_t **conflict_p,
result_pool,
scratch_pool));
- 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);
+ if (old_version)
+ 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,
@@ -491,7 +550,7 @@ create_node_tree_conflict(svn_skel_t **conflict_p,
update_move_baton_t *umb = nmb->umb;
const char *dst_repos_relpath;
const char *dst_root_relpath = svn_relpath_prefix(nmb->dst_relpath,
- nmb->umb->dst_op_depth,
+ umb->dst_op_depth,
scratch_pool);
dst_repos_relpath =
@@ -500,8 +559,6 @@ create_node_tree_conflict(svn_skel_t **conflict_p,
nmb->dst_relpath),
scratch_pool);
-
-
return svn_error_trace(
create_tree_conflict(conflict_p, umb->wcroot, dst_local_relpath,
svn_relpath_prefix(dst_local_relpath,
@@ -737,6 +794,111 @@ tc_editor_add_directory(node_move_baton_t *nmb,
}
static svn_error_t *
+copy_working_node(const char *src_relpath,
+ const char *dst_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *dst_parent_relpath = svn_relpath_dirname(dst_relpath,
+ scratch_pool);
+
+ /* Add a WORKING row for the new node, based on the source. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt,wcroot->sdb,
+ STMT_INSERT_WORKING_NODE_COPY_FROM));
+ SVN_ERR(svn_sqlite__bindf(stmt, "issdst", wcroot->wc_id, src_relpath,
+ dst_relpath, relpath_depth(dst_relpath),
+ dst_parent_relpath, presence_map,
+ svn_wc__db_status_normal));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Copy properties over. ### This loses changelist association. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, src_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ apr_size_t props_size;
+ const char *properties;
+
+ properties = svn_sqlite__column_blob(stmt, 1, &props_size,
+ scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "issbs",
+ wcroot->wc_id, dst_relpath,
+ svn_relpath_dirname(dst_relpath,
+ scratch_pool),
+ properties, props_size, NULL));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_incoming_add_directory(node_move_baton_t *nmb,
+ const char *dst_relpath,
+ svn_node_kind_t old_kind,
+ apr_hash_t *props,
+ const char *src_relpath,
+ apr_pool_t *scratch_pool)
+{
+ update_move_baton_t *b = nmb->umb;
+ const char *dst_abspath;
+ svn_node_kind_t wc_kind;
+ svn_skel_t *work_item = NULL;
+ svn_skel_t *conflict = NULL;
+ svn_wc_conflict_reason_t reason = svn_wc_conflict_reason_unversioned;
+
+ SVN_ERR(mark_parent_edited(nmb, scratch_pool));
+ if (nmb->skip)
+ return SVN_NO_ERROR;
+
+ dst_abspath = svn_dirent_join(b->wcroot->abspath, dst_relpath, scratch_pool);
+
+ /* Check for unversioned tree-conflict */
+ SVN_ERR(svn_io_check_path(dst_abspath, &wc_kind, scratch_pool));
+
+ if (wc_kind == old_kind)
+ wc_kind = svn_node_none; /* Node will be gone once we install */
+
+ if (wc_kind != svn_node_none && wc_kind != old_kind) /* replace */
+ {
+ SVN_ERR(create_node_tree_conflict(&conflict, nmb, dst_relpath,
+ old_kind, svn_node_dir,
+ reason,
+ (old_kind == svn_node_none)
+ ? svn_wc_conflict_action_add
+ : svn_wc_conflict_action_replace,
+ NULL,
+ scratch_pool, scratch_pool));
+ nmb->skip = TRUE;
+ }
+ else
+ {
+ SVN_ERR(copy_working_node(src_relpath, dst_relpath, b->wcroot,
+ scratch_pool));
+ SVN_ERR(svn_wc__wq_build_dir_install(&work_item, b->db, dst_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(update_move_list_add(b->wcroot, dst_relpath, b->db,
+ (old_kind == svn_node_none)
+ ? svn_wc_notify_update_add
+ : svn_wc_notify_update_replace,
+ svn_node_dir,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable,
+ conflict, work_item, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
tc_editor_add_file(node_move_baton_t *nmb,
const char *relpath,
svn_node_kind_t old_kind,
@@ -824,6 +986,85 @@ tc_editor_add_file(node_move_baton_t *nmb,
return SVN_NO_ERROR;
}
+static svn_error_t *
+tc_editor_incoming_add_file(node_move_baton_t *nmb,
+ const char *dst_relpath,
+ svn_node_kind_t old_kind,
+ const svn_checksum_t *checksum,
+ apr_hash_t *props,
+ const char *src_relpath,
+ const char *content_abspath,
+ apr_pool_t *scratch_pool)
+{
+ update_move_baton_t *b = nmb->umb;
+ svn_wc_conflict_reason_t reason = svn_wc_conflict_reason_unversioned;
+ svn_node_kind_t wc_kind;
+ const char *dst_abspath;
+ svn_skel_t *work_items = NULL;
+ svn_skel_t *work_item = NULL;
+ svn_skel_t *conflict = NULL;
+
+ SVN_ERR(mark_parent_edited(nmb, scratch_pool));
+ if (nmb->skip)
+ {
+ SVN_ERR(svn_io_remove_file2(content_abspath, TRUE, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ dst_abspath = svn_dirent_join(b->wcroot->abspath, dst_relpath, scratch_pool);
+
+ /* Check for unversioned tree-conflict */
+ SVN_ERR(svn_io_check_path(dst_abspath, &wc_kind, scratch_pool));
+
+ if (wc_kind != svn_node_none && wc_kind != old_kind) /* replace */
+ {
+ SVN_ERR(create_node_tree_conflict(&conflict, nmb, dst_relpath,
+ old_kind, svn_node_file,
+ reason,
+ (old_kind == svn_node_none)
+ ? svn_wc_conflict_action_add
+ : svn_wc_conflict_action_replace,
+ NULL,
+ scratch_pool, scratch_pool));
+ nmb->skip = TRUE;
+ SVN_ERR(svn_io_remove_file2(content_abspath, TRUE, scratch_pool));
+ }
+ else
+ {
+ const char *src_abspath;
+
+ SVN_ERR(copy_working_node(src_relpath, dst_relpath, b->wcroot,
+ scratch_pool));
+
+ /* Update working file. */
+ src_abspath = svn_dirent_join(b->wcroot->abspath, src_relpath,
+ scratch_pool);
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, b->db, dst_abspath,
+ src_abspath,
+ 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);
+
+ /* Queue removal of temporary content copy. */
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, b->db,
+ b->wcroot->abspath, src_abspath,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ SVN_ERR(update_move_list_add(b->wcroot, dst_relpath, b->db,
+ (old_kind == svn_node_none)
+ ? svn_wc_notify_update_add
+ : svn_wc_notify_update_replace,
+ svn_node_file,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable,
+ conflict, work_items, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
/* All the info we need about one version of a working node. */
typedef struct working_node_version_t
{
@@ -860,6 +1101,11 @@ create_conflict_markers(svn_skel_t **work_items,
part = svn_relpath_skip_ancestor(original_version->path_in_repos,
repos_relpath);
+ if (part == NULL)
+ part = svn_relpath_skip_ancestor(conflicted_version->path_in_repos,
+ repos_relpath);
+ SVN_ERR_ASSERT(part != NULL);
+
conflicted_version->path_in_repos
= svn_relpath_join(conflicted_version->path_in_repos, part, scratch_pool);
original_version->path_in_repos = repos_relpath;
@@ -873,6 +1119,13 @@ create_conflict_markers(svn_skel_t **work_items,
conflicted_version,
scratch_pool, scratch_pool));
}
+ else if (operation == svn_wc_operation_merge)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(
+ conflict_skel, original_version,
+ conflicted_version,
+ scratch_pool, scratch_pool));
+ }
else
{
SVN_ERR(svn_wc__conflict_skel_set_op_switch(
@@ -1197,6 +1450,217 @@ tc_editor_alter_file(node_move_baton_t *nmb,
}
static svn_error_t *
+tc_editor_update_incoming_moved_file(node_move_baton_t *nmb,
+ const char *dst_relpath,
+ const char *src_relpath,
+ const svn_checksum_t *src_checksum,
+ const svn_checksum_t *dst_checksum,
+ apr_hash_t *dst_props,
+ apr_hash_t *src_props,
+ svn_boolean_t do_text_merge,
+ apr_pool_t *scratch_pool)
+{
+ update_move_baton_t *b = nmb->umb;
+ working_node_version_t old_version, new_version;
+ const char *dst_abspath = svn_dirent_join(b->wcroot->abspath,
+ dst_relpath,
+ scratch_pool);
+ svn_skel_t *conflict_skel = NULL;
+ enum svn_wc_merge_outcome_t merge_outcome;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_unchanged;
+ svn_wc_notify_state_t content_state = svn_wc_notify_state_unchanged;
+ svn_skel_t *work_item, *work_items = NULL;
+ svn_node_kind_t dst_kind_on_disk;
+ const char *dst_repos_relpath;
+ svn_boolean_t tree_conflict = FALSE;
+ svn_node_kind_t dst_db_kind;
+ svn_error_t *err;
+
+ SVN_ERR(mark_node_edited(nmb, scratch_pool));
+ if (nmb->skip)
+ return SVN_NO_ERROR;
+
+ err = svn_wc__db_base_get_info_internal(NULL, &dst_db_kind, NULL,
+ &dst_repos_relpath,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, dst_relpath,
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ const char *dst_parent_relpath;
+ const char *dst_parent_repos_relpath;
+ const char *src_abspath;
+
+ /* If the file cannot be found, it was either deleted at the
+ * move destination, or it was moved after its parent was moved.
+ * We cannot deal with this problem right now. Instead, we will
+ * raise a new tree conflict at the location where this file should
+ * have been, and let another run of the resolver deal with the
+ * new conflict later on. */
+
+ svn_error_clear(err);
+
+ /* Create a WORKING node for this file at the move destination. */
+ SVN_ERR(copy_working_node(src_relpath, dst_relpath, b->wcroot,
+ scratch_pool));
+
+ /* Raise a tree conflict at the new WORKING node. */
+ dst_db_kind = svn_node_none;
+ SVN_ERR(create_node_tree_conflict(&conflict_skel, nmb, dst_relpath,
+ svn_node_file, dst_db_kind,
+ svn_wc_conflict_reason_edited,
+ svn_wc_conflict_action_delete,
+ NULL, scratch_pool, scratch_pool));
+ dst_parent_relpath = svn_relpath_dirname(dst_relpath, scratch_pool);
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &dst_parent_repos_relpath,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, b->wcroot,
+ dst_parent_relpath,
+ scratch_pool, scratch_pool));
+ dst_repos_relpath = svn_relpath_join(dst_parent_repos_relpath,
+ svn_relpath_basename(dst_relpath,
+ scratch_pool),
+ scratch_pool);
+ tree_conflict = TRUE;
+
+ /* Schedule a copy of the victim's file content to the new node's path. */
+ src_abspath = svn_dirent_join(b->wcroot->abspath, src_relpath,
+ scratch_pool);
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, b->db,
+ dst_abspath,
+ src_abspath,
+ 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);
+ }
+ else
+ SVN_ERR(err);
+
+ if ((dst_db_kind == svn_node_none || dst_db_kind != svn_node_file) &&
+ conflict_skel == NULL)
+ {
+ SVN_ERR(create_node_tree_conflict(&conflict_skel, nmb, dst_relpath,
+ svn_node_file, dst_db_kind,
+ dst_db_kind == svn_node_none
+ ? svn_wc_conflict_reason_missing
+ : svn_wc_conflict_reason_obstructed,
+ svn_wc_conflict_action_edit,
+ NULL,
+ scratch_pool, scratch_pool));
+ tree_conflict = TRUE;
+ }
+
+ SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind_on_disk, scratch_pool));
+ if ((dst_kind_on_disk == svn_node_none || dst_kind_on_disk != svn_node_file)
+ && conflict_skel == NULL)
+ {
+ SVN_ERR(create_node_tree_conflict(&conflict_skel, nmb, dst_relpath,
+ svn_node_file, dst_kind_on_disk,
+ dst_kind_on_disk == svn_node_none
+ ? svn_wc_conflict_reason_missing
+ : svn_wc_conflict_reason_obstructed,
+ svn_wc_conflict_action_edit,
+ NULL,
+ scratch_pool, scratch_pool));
+ tree_conflict = TRUE;
+ }
+
+ old_version.location_and_kind = b->old_version;
+ new_version.location_and_kind = b->new_version;
+
+ old_version.checksum = src_checksum;
+ old_version.props = src_props;
+ new_version.checksum = dst_checksum;
+ new_version.props = dst_props;
+
+ /* Merge properties and text content if there is no tree conflict. */
+ if (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, dst_relpath,
+ &old_version, &new_version,
+ scratch_pool, scratch_pool));
+ if (do_text_merge)
+ {
+ const char *old_pristine_abspath;
+ const char *src_abspath;
+ const char *label_left;
+ const char *label_target;
+
+ /*
+ * Run a 3-way merge to update the file at its post-move location,
+ * using the pre-move file's pristine text as the merge base, the
+ * post-move content as the merge-right version, and the current
+ * content of the working file at the pre-move location as the
+ * merge-left version.
+ */
+ SVN_ERR(svn_wc__db_pristine_get_path(&old_pristine_abspath,
+ b->db, b->wcroot->abspath,
+ src_checksum,
+ scratch_pool, scratch_pool));
+ src_abspath = svn_dirent_join(b->wcroot->abspath, src_relpath,
+ scratch_pool);
+ label_left = apr_psprintf(scratch_pool, ".r%ld",
+ b->old_version->peg_rev);
+ label_target = apr_psprintf(scratch_pool, ".r%ld",
+ b->new_version->peg_rev);
+ SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel,
+ &merge_outcome, b->db,
+ old_pristine_abspath,
+ src_abspath,
+ dst_abspath,
+ dst_abspath,
+ label_left,
+ _(".working"),
+ label_target,
+ actual_props,
+ FALSE, /* dry-run */
+ NULL, /* diff3-cmd */
+ NULL, /* merge options */
+ propchanges,
+ b->cancel_func, b->cancel_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;
+ }
+ }
+
+ /* If there are any conflicts to be stored, convert them into work items
+ * too. */
+ if (conflict_skel)
+ {
+ SVN_ERR(create_conflict_markers(&work_item, dst_abspath, b->db,
+ dst_repos_relpath, conflict_skel,
+ b->operation, &old_version, &new_version,
+ svn_node_file, !tree_conflict,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ SVN_ERR(update_move_list_add(b->wcroot, dst_relpath, b->db,
+ svn_wc_notify_update_update,
+ svn_node_file,
+ content_state,
+ prop_state,
+ conflict_skel, work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
tc_editor_delete(node_move_baton_t *nmb,
const char *relpath,
svn_node_kind_t old_kind,
@@ -1327,6 +1791,93 @@ tc_editor_delete(node_move_baton_t *nmb,
return SVN_NO_ERROR;
}
+/* Handle node deletion for an incoming move. */
+static svn_error_t *
+tc_incoming_editor_delete(node_move_baton_t *nmb,
+ const char *relpath,
+ svn_node_kind_t old_kind,
+ svn_node_kind_t new_kind,
+ apr_pool_t *scratch_pool)
+{
+ update_move_baton_t *b = nmb->umb;
+ svn_sqlite__stmt_t *stmt;
+ const char *local_abspath;
+ svn_boolean_t is_modified, is_all_deletes;
+ svn_skel_t *work_items = NULL;
+ svn_skel_t *conflict = NULL;
+
+ SVN_ERR(mark_parent_edited(nmb, scratch_pool));
+ if (nmb->skip)
+ return SVN_NO_ERROR;
+
+ /* Check before retracting delete to catch delete-delete
+ conflicts. This catches conflicts on the node itself; deleted
+ children are caught as local modifications below.*/
+ if (nmb->shadowed)
+ {
+ SVN_ERR(mark_tc_on_op_root(nmb,
+ old_kind, new_kind,
+ svn_wc_conflict_action_delete,
+ scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ local_abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool);
+ SVN_ERR(svn_wc__node_has_local_mods(&is_modified, &is_all_deletes,
+ nmb->umb->db, local_abspath, FALSE,
+ NULL, NULL, scratch_pool));
+ if (is_modified)
+ {
+ svn_wc_conflict_reason_t reason;
+
+ /* No conflict means no NODES rows at the relpath op-depth
+ so it's easy to convert the modified tree into a copy.
+
+ Note the following assumptions for relpath:
+ * it is not shadowed
+ * it is not the/an op-root. (or we can't make us a copy)
+ */
+
+ SVN_ERR(svn_wc__db_op_make_copy_internal(b->wcroot, relpath, FALSE,
+ NULL, NULL, scratch_pool));
+
+ reason = svn_wc_conflict_reason_edited;
+
+ SVN_ERR(create_node_tree_conflict(&conflict, nmb, relpath,
+ old_kind, new_kind, reason,
+ (new_kind == svn_node_none)
+ ? svn_wc_conflict_action_delete
+ : svn_wc_conflict_action_replace,
+ NULL,
+ scratch_pool, scratch_pool));
+ nmb->skip = TRUE;
+ }
+ else
+ {
+ /* Delete the WORKING node at DST_RELPATH. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_INSERT_DELETE_FROM_NODE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd",
+ b->wcroot->wc_id, relpath,
+ 0, relpath_depth(relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Only notify if add_file/add_dir is not going to notify */
+ if (conflict || (new_kind == svn_node_none))
+ SVN_ERR(update_move_list_add(b->wcroot, relpath, b->db,
+ svn_wc_notify_update_delete,
+ new_kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable,
+ conflict, work_items, scratch_pool));
+ else if (work_items)
+ SVN_ERR(svn_wc__db_wq_add_internal(b->wcroot, work_items,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
/*
* Driver code.
*
@@ -1618,28 +2169,30 @@ suitable_for_move(svn_wc__db_wcroot_t *wcroot,
while (have_row)
{
svn_revnum_t node_revision = svn_sqlite__column_revnum(stmt, 2);
- const char *relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *relpath;
svn_pool_clear(iterpool);
- relpath = svn_relpath_skip_ancestor(local_relpath, relpath);
+ relpath = svn_relpath_skip_ancestor(local_relpath, child_relpath);
relpath = svn_relpath_join(repos_relpath, relpath, iterpool);
- if (revision != node_revision)
+ 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 mixed-revision working copy"),
- path_for_error_message(wcroot, local_relpath,
+ _("Cannot apply update because '%s' is a "
+ "switched path (please switch it back to "
+ "its original URL and try again)"),
+ path_for_error_message(wcroot, child_relpath,
scratch_pool));
- if (strcmp(relpath, svn_sqlite__column_text(stmt, 1, NULL)))
+ 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 switched subtree"),
- path_for_error_message(wcroot,
- local_relpath,
+ _("Cannot apply update because '%s' is a "
+ "mixed-revision working copy (please "
+ "update and try again)"),
+ path_for_error_message(wcroot, local_relpath,
scratch_pool));
SVN_ERR(svn_sqlite__step(&have_row, stmt));
@@ -1827,6 +2380,1236 @@ svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db,
return SVN_NO_ERROR;
}
+static svn_error_t *
+get_working_info(apr_hash_t **props,
+ const svn_checksum_t **checksum,
+ apr_array_header_t **children,
+ svn_node_kind_t *kind,
+ const char *local_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ const char *repos_relpath;
+ svn_node_kind_t db_kind;
+ svn_error_t *err;
+
+ err = svn_wc__db_read_info_internal(&status, &db_kind, NULL, &repos_relpath,
+ NULL, NULL, NULL, NULL, NULL,
+ checksum,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ result_pool, scratch_pool);
+
+ /* If there is no node, or only a node that describes a delete
+ of a lower layer we report this node as not existing. */
+ if ((err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ || (!err && status != svn_wc__db_status_added
+ && status != svn_wc__db_status_normal))
+ {
+ svn_error_clear(err);
+
+ if (kind)
+ *kind = svn_node_none;
+ if (checksum)
+ *checksum = NULL;
+ if (props)
+ *props = NULL;
+ if (children)
+ *children = apr_array_make(result_pool, 0, sizeof(const char *));
+
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ SVN_ERR(svn_wc__db_read_props_internal(props, wcroot, local_relpath,
+ result_pool, scratch_pool));
+
+ if (kind)
+ *kind = db_kind;
+
+ if (children && db_kind == svn_node_dir)
+ {
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *children = apr_array_make(result_pool, 16, sizeof(const char *));
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ APR_ARRAY_PUSH(*children, const char *)
+ = svn_relpath_basename(child_relpath, result_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ else if (children)
+ *children = apr_array_make(result_pool, 0, sizeof(const char *));
+
+ return SVN_NO_ERROR;
+}
+
+/* Apply changes found in the victim node at SRC_RELPATH to the incoming
+ * move at DST_RELPATH. */
+static svn_error_t *
+update_incoming_moved_node(node_move_baton_t *nmb,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *src_relpath,
+ const char *dst_relpath,
+ apr_pool_t *scratch_pool)
+{
+ update_move_baton_t *b = nmb->umb;
+ svn_node_kind_t orig_kind, working_kind;
+ const char *victim_relpath = src_relpath;
+ const svn_checksum_t *orig_checksum, *working_checksum;
+ apr_hash_t *orig_props, *working_props;
+ apr_array_header_t *orig_children, *working_children;
+
+ if (b->cancel_func)
+ SVN_ERR(b->cancel_func(b->cancel_baton));
+
+ /* Compare the tree conflict victim's copied layer (the "original") with
+ * the working layer, i.e. look for changes layered on top of the copy. */
+ SVN_ERR(get_info(&orig_props, &orig_checksum, &orig_children, &orig_kind,
+ victim_relpath, b->src_op_depth, wcroot, scratch_pool,
+ scratch_pool));
+ SVN_ERR(get_working_info(&working_props, &working_checksum,
+ &working_children, &working_kind, victim_relpath,
+ wcroot, scratch_pool, scratch_pool));
+
+ if (working_kind == svn_node_none
+ || (orig_kind != svn_node_none && orig_kind != working_kind))
+ {
+ SVN_ERR(tc_incoming_editor_delete(nmb, dst_relpath, orig_kind,
+ working_kind, scratch_pool));
+ }
+
+ if (nmb->skip)
+ return SVN_NO_ERROR;
+
+ if (working_kind != svn_node_none && orig_kind != working_kind)
+ {
+ if (working_kind == svn_node_file || working_kind == svn_node_symlink)
+ {
+ const char *victim_abspath;
+ const char *wctemp_abspath;
+ svn_stream_t *working_stream;
+ svn_stream_t *temp_stream;
+ const char *temp_abspath;
+ svn_error_t *err;
+
+ /* Copy the victim's content to a safe place and add it from there. */
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&wctemp_abspath, b->db,
+ b->wcroot->abspath,
+ scratch_pool,
+ scratch_pool));
+ victim_abspath = svn_dirent_join(b->wcroot->abspath,
+ victim_relpath, scratch_pool);
+ SVN_ERR(svn_stream_open_readonly(&working_stream, victim_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_unique(&temp_stream, &temp_abspath,
+ wctemp_abspath, svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ err = svn_stream_copy3(working_stream, temp_stream,
+ b->cancel_func, b->cancel_baton,
+ scratch_pool);
+ if (err && err->apr_err == SVN_ERR_CANCELLED)
+ {
+ svn_error_t *err2;
+
+ err2 = svn_io_remove_file2(temp_abspath, TRUE, scratch_pool);
+ return svn_error_compose_create(err, err2);
+ }
+ else
+ SVN_ERR(err);
+
+ SVN_ERR(tc_editor_incoming_add_file(nmb, dst_relpath, orig_kind,
+ working_checksum, working_props,
+ victim_relpath, temp_abspath,
+ scratch_pool));
+ }
+ else if (working_kind == svn_node_dir)
+ {
+ SVN_ERR(tc_editor_incoming_add_directory(nmb, dst_relpath,
+ orig_kind, working_props,
+ victim_relpath,
+ scratch_pool));
+ }
+ }
+ else if (working_kind != svn_node_none)
+ {
+ svn_boolean_t props_equal;
+
+ SVN_ERR(props_match(&props_equal, orig_props, working_props,
+ scratch_pool));
+
+ if (working_kind == svn_node_file || working_kind == svn_node_symlink)
+ {
+ svn_boolean_t is_modified;
+
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_modified, b->db,
+ svn_dirent_join(
+ b->wcroot->abspath,
+ victim_relpath,
+ scratch_pool),
+ FALSE /* exact_comparison */,
+ scratch_pool));
+ if (!props_equal || is_modified)
+ SVN_ERR(tc_editor_update_incoming_moved_file(nmb, dst_relpath,
+ victim_relpath,
+ working_checksum,
+ orig_checksum,
+ orig_props,
+ working_props,
+ is_modified,
+ scratch_pool));
+ }
+ else if (working_kind == svn_node_dir)
+ {
+ if (!props_equal)
+ SVN_ERR(tc_editor_alter_directory(nmb, dst_relpath,
+ orig_props, working_props,
+ scratch_pool));
+ }
+ }
+
+ if (nmb->skip)
+ return SVN_NO_ERROR;
+
+ if (working_kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i = 0, j = 0;
+
+ while (i < orig_children->nelts || j < working_children->nelts)
+ {
+ const char *child_name;
+ svn_boolean_t orig_only = FALSE, working_only = FALSE;
+ node_move_baton_t cnmb = { 0 };
+
+ cnmb.pb = nmb;
+ cnmb.umb = nmb->umb;
+ cnmb.shadowed = nmb->shadowed;
+
+ svn_pool_clear(iterpool);
+ if (i >= orig_children->nelts)
+ {
+ working_only = TRUE;
+ child_name = APR_ARRAY_IDX(working_children, j, const char *);
+ }
+ else if (j >= working_children->nelts)
+ {
+ orig_only = TRUE;
+ child_name = APR_ARRAY_IDX(orig_children, i, const char *);
+ }
+ else
+ {
+ const char *orig_name = APR_ARRAY_IDX(orig_children, i,
+ const char *);
+ const char *working_name = APR_ARRAY_IDX(working_children, j,
+ const char *);
+ int cmp = strcmp(orig_name, working_name);
+
+ if (cmp > 0)
+ working_only = TRUE;
+ else if (cmp < 0)
+ orig_only = TRUE;
+
+ child_name = working_only ? working_name : orig_name;
+ }
+
+ cnmb.src_relpath = svn_relpath_join(src_relpath, child_name,
+ iterpool);
+ cnmb.dst_relpath = svn_relpath_join(dst_relpath, child_name,
+ iterpool);
+
+ SVN_ERR(update_incoming_moved_node(&cnmb, wcroot, cnmb.src_relpath,
+ cnmb.dst_relpath, iterpool));
+
+ if (!working_only)
+ ++i;
+ if (!orig_only)
+ ++j;
+
+ if (nmb->skip) /* Does parent now want a skip? */
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_update_incoming_move(). */
+static svn_error_t *
+update_incoming_move(svn_revnum_t *old_rev,
+ svn_revnum_t *new_rev,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *dst_relpath,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_action_t action,
+ svn_wc_conflict_reason_t reason,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ update_move_baton_t umb = { NULL };
+ svn_wc_conflict_version_t old_version;
+ svn_wc_conflict_version_t new_version;
+ apr_int64_t repos_id;
+ node_move_baton_t nmb = { 0 };
+ svn_boolean_t is_modified;
+
+ SVN_ERR_ASSERT(svn_relpath_skip_ancestor(dst_relpath, local_relpath) == NULL);
+
+ /* For incoming moves during update/switch, the move source is a copied
+ * tree which was copied from the pre-update BASE revision while raising
+ * the tree conflict, when the update attempted to delete the move source.
+ * This copy is our "original" state (SRC of the diff) and the local changes
+ * on top of this copy at the top-most WORKING layer are used to drive the
+ * editor (DST of the diff).
+ *
+ * The move destination, where changes are applied to, is now in the BASE
+ * tree at DST_RELPATH. This repository-side move is the "incoming change"
+ * recorded for any tree conflicts created during the editor drive.
+ * We assume this path contains no local changes, and create local changes
+ * in DST_RELPATH corresponding to changes contained in the conflict victim.
+ *
+ * DST_OP_DEPTH is used to infer the "op-root" of the incoming move. This
+ * "op-root" is virtual because all nodes belonging to the incoming move
+ * live in the BASE tree. It is used for constructing repository paths
+ * when new tree conflicts need to be raised.
+ */
+ umb.src_op_depth = relpath_depth(local_relpath); /* SRC of diff */
+ umb.dst_op_depth = relpath_depth(dst_relpath); /* virtual DST op-root */
+
+ SVN_ERR(verify_write_lock(wcroot, local_relpath, scratch_pool));
+ SVN_ERR(verify_write_lock(wcroot, dst_relpath, scratch_pool));
+
+ /* Make sure there are no local modifications in the move destination. */
+ SVN_ERR(svn_wc__node_has_local_mods(&is_modified, NULL, db,
+ svn_dirent_join(wcroot->abspath,
+ dst_relpath,
+ scratch_pool),
+ TRUE, cancel_func, cancel_baton,
+ scratch_pool));
+ if (is_modified)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot merge local changes from '%s' because "
+ "'%s' already contains other local changes "
+ "(please commit or revert these other changes "
+ "and try again)"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool),
+ scratch_pool),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath, dst_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ /* Check for switched subtrees and mixed-revision working copy. */
+ SVN_ERR(suitable_for_move(wcroot, dst_relpath, scratch_pool));
+
+ /* Read version info from the updated incoming post-move location. */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &new_version.node_kind,
+ &new_version.peg_rev,
+ &new_version.path_in_repos,
+ &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, dst_relpath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(&new_version.repos_url,
+ &new_version.repos_uuid,
+ wcroot, repos_id,
+ scratch_pool));
+
+ /* Read version info from the victim's location. */
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &old_version.node_kind,
+ &old_version.peg_rev,
+ &old_version.path_in_repos, &repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, wcroot,
+ local_relpath, umb.src_op_depth,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(&old_version.repos_url,
+ &old_version.repos_uuid,
+ wcroot, repos_id,
+ scratch_pool));
+ *old_rev = old_version.peg_rev;
+ *new_rev = new_version.peg_rev;
+
+ umb.operation = operation;
+ umb.old_version= &old_version;
+ umb.new_version= &new_version;
+ umb.db = db;
+ umb.wcroot = wcroot;
+ umb.cancel_func = cancel_func;
+ umb.cancel_baton = cancel_baton;
+
+ /* Create a new, and empty, list for notification information. */
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ /* Drive the editor... */
+
+ nmb.umb = &umb;
+ nmb.src_relpath = local_relpath;
+ nmb.dst_relpath = dst_relpath;
+ /* nmb.shadowed = FALSE; */
+ /* nmb.edited = FALSE; */
+ /* nmb.skip_children = FALSE; */
+
+ /* We walk the conflict victim, comparing each node with the equivalent node
+ * at the WORKING layer, applying any local changes to nodes at the move
+ * destination. */
+ SVN_ERR(update_incoming_moved_node(&nmb, wcroot, local_relpath, dst_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_update_incoming_move(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *dest_abspath,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_action_t action,
+ svn_wc_conflict_reason_t reason,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_revnum_t old_rev, new_rev;
+ const char *local_relpath;
+ const char *dest_relpath;
+
+ /* ### Check for mixed-rev src or dst? */
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ dest_relpath
+ = svn_dirent_skip_ancestor(wcroot->abspath, dest_abspath);
+
+ SVN_WC__DB_WITH_TXN(update_incoming_move(&old_rev, &new_rev, db, wcroot,
+ local_relpath, dest_relpath,
+ operation, action, reason,
+ cancel_func, cancel_baton,
+ scratch_pool),
+ wcroot);
+
+ /* Send all queued up notifications. */
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, old_rev, new_rev,
+ 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_rev;
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+
+ return SVN_NO_ERROR;
+}
+
+typedef struct update_local_add_baton_t {
+ int add_op_depth;
+ svn_wc__db_t *db;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* We refer to these if raising new tree conflicts. */
+ const svn_wc_conflict_version_t *new_version;
+} update_local_add_baton_t;
+
+typedef struct added_node_baton_t {
+ struct update_local_add_baton_t *b;
+ struct added_node_baton_t *pb;
+ const char *local_relpath;
+ svn_boolean_t skip;
+ svn_boolean_t edited;
+} added_node_baton_t;
+
+
+static svn_error_t *
+update_local_add_mark_node_edited(added_node_baton_t *nb,
+ apr_pool_t *scratch_pool)
+{
+ if (nb->edited)
+ return SVN_NO_ERROR;
+
+ if (nb->pb)
+ {
+ SVN_ERR(update_local_add_mark_node_edited(nb->pb, scratch_pool));
+
+ if (nb->pb->skip)
+ nb->skip = TRUE;
+ }
+
+ nb->edited = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_local_add_mark_parent_edited(added_node_baton_t *nb,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(nb && nb->pb);
+
+ SVN_ERR(update_local_add_mark_node_edited(nb->pb, scratch_pool));
+
+ if (nb->pb->skip)
+ nb->skip = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+mark_update_add_add_tree_conflict(added_node_baton_t *nb,
+ svn_node_kind_t base_kind,
+ svn_node_kind_t working_kind,
+ svn_wc_conflict_reason_t local_change,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+
+{
+ svn_wc__db_t *db = nb->b->db;
+ svn_wc__db_wcroot_t *wcroot = nb->b->wcroot;
+ svn_wc_conflict_version_t *new_version;
+ svn_skel_t *conflict;
+
+ new_version = svn_wc_conflict_version_dup(nb->b->new_version, result_pool);
+
+ /* Fill in conflict info templates with info for this node. */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, &new_version->peg_rev,
+ &new_version->path_in_repos,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, nb->local_relpath,
+ scratch_pool, scratch_pool));
+ new_version->node_kind = base_kind;
+
+ SVN_ERR(create_tree_conflict(&conflict, wcroot, nb->local_relpath,
+ nb->local_relpath, db, NULL, new_version,
+ svn_wc_operation_update,
+ svn_node_none, base_kind, NULL,
+ local_change, svn_wc_conflict_action_add,
+ NULL, scratch_pool, scratch_pool));
+
+ SVN_ERR(update_move_list_add(wcroot, nb->local_relpath, db,
+ svn_wc_notify_tree_conflict, working_kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable,
+ conflict, NULL, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_local_add_notify_obstructed_or_missing(added_node_baton_t *nb,
+ svn_node_kind_t working_kind,
+ svn_node_kind_t kind_on_disk,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_notify_state_t content_state;
+
+ if (kind_on_disk == svn_node_none)
+ content_state = svn_wc_notify_state_missing;
+ else
+ content_state = svn_wc_notify_state_obstructed;
+
+ SVN_ERR(update_move_list_add(nb->b->wcroot, nb->local_relpath, nb->b->db,
+ svn_wc_notify_skip, working_kind,
+ content_state, svn_wc_notify_state_inapplicable,
+ NULL, NULL, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_update_add_new_file(added_node_baton_t *nb,
+ svn_node_kind_t base_kind,
+ const svn_checksum_t *base_checksum,
+ apr_hash_t *base_props,
+ svn_node_kind_t working_kind,
+ const svn_checksum_t *working_checksum,
+ apr_hash_t *working_props,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ svn_node_kind_t kind_on_disk;
+
+ SVN_ERR(update_local_add_mark_parent_edited(nb, scratch_pool));
+ if (nb->skip)
+ return SVN_NO_ERROR;
+
+ if (base_kind != svn_node_none)
+ {
+ SVN_ERR(mark_update_add_add_tree_conflict(nb, base_kind, svn_node_file,
+ svn_wc_conflict_reason_added,
+ scratch_pool, scratch_pool));
+ nb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Check for obstructions. */
+ local_abspath = svn_dirent_join(nb->b->wcroot->abspath, nb->local_relpath,
+ scratch_pool);
+ SVN_ERR(svn_io_check_path(local_abspath, &kind_on_disk, scratch_pool));
+ if (kind_on_disk != svn_node_file)
+ {
+ SVN_ERR(update_local_add_notify_obstructed_or_missing(nb, working_kind,
+ kind_on_disk,
+ scratch_pool));
+ nb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Nothing else to do. Locally added files are an op-root in NODES. */
+
+ SVN_ERR(update_move_list_add(nb->b->wcroot, nb->local_relpath, nb->b->db,
+ svn_wc_notify_update_add, svn_node_file,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable,
+ NULL, NULL, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_update_add_new_directory(added_node_baton_t *nb,
+ svn_node_kind_t base_kind,
+ apr_hash_t *base_props,
+ apr_hash_t *working_props,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ svn_node_kind_t kind_on_disk;
+
+ SVN_ERR(update_local_add_mark_parent_edited(nb, scratch_pool));
+ if (nb->skip)
+ return SVN_NO_ERROR;
+
+ if (base_kind != svn_node_none)
+ {
+ SVN_ERR(mark_update_add_add_tree_conflict(nb, base_kind, svn_node_dir,
+ svn_wc_conflict_reason_added,
+ scratch_pool, scratch_pool));
+ nb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Check for obstructions. */
+ local_abspath = svn_dirent_join(nb->b->wcroot->abspath, nb->local_relpath,
+ scratch_pool);
+ SVN_ERR(svn_io_check_path(local_abspath, &kind_on_disk, scratch_pool));
+ if (kind_on_disk != svn_node_dir)
+ {
+ SVN_ERR(update_local_add_notify_obstructed_or_missing(nb, svn_node_dir,
+ kind_on_disk,
+ scratch_pool));
+ nb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Nothing else to do. Locally added directories are an op-root in NODES. */
+
+ SVN_ERR(update_move_list_add(nb->b->wcroot, nb->local_relpath, nb->b->db,
+ svn_wc_notify_update_add, svn_node_dir,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable,
+ NULL, NULL, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_incoming_add_merge_props(svn_wc_notify_state_t *prop_state,
+ svn_skel_t **conflict_skel,
+ const char *local_relpath,
+ apr_hash_t *base_props,
+ apr_hash_t *working_props,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *new_actual_props;
+ apr_array_header_t *propchanges;
+ const char *local_abspath = svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool);
+
+ /*
+ * Run a 3-way prop merge to update the props, using the empty props
+ * as the merge base, the post-update props as the merge-left version, and
+ * the current props of the added working file as the merge-right version.
+ */
+ SVN_ERR(svn_prop_diffs(&propchanges, working_props,
+ apr_hash_make(scratch_pool), scratch_pool));
+ SVN_ERR(svn_wc__merge_props(conflict_skel, prop_state, &new_actual_props,
+ db, local_abspath,
+ apr_hash_make(scratch_pool),
+ base_props, working_props, propchanges,
+ result_pool, scratch_pool));
+
+ /* Install the new actual props. */
+ if (apr_hash_count(new_actual_props) > 0)
+ SVN_ERR(svn_wc__db_op_set_props_internal(wcroot, local_relpath,
+ new_actual_props,
+ svn_wc__has_magic_property(
+ propchanges),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_update_add_merge_files(added_node_baton_t *nb,
+ const svn_checksum_t *working_checksum,
+ const svn_checksum_t *base_checksum,
+ apr_hash_t *working_props,
+ apr_hash_t *base_props,
+ apr_pool_t *scratch_pool)
+{
+ update_local_add_baton_t *b = nb->b;
+ apr_array_header_t *propchanges;
+ svn_boolean_t is_modified;
+ enum svn_wc_merge_outcome_t merge_outcome;
+ svn_skel_t *conflict_skel = NULL;
+ svn_wc_notify_state_t prop_state, content_state;
+ svn_skel_t *work_items = NULL;
+ svn_node_kind_t kind_on_disk;
+ const char *local_abspath = svn_dirent_join(b->wcroot->abspath,
+ nb->local_relpath,
+ scratch_pool);
+
+ SVN_ERR(update_local_add_mark_node_edited(nb, scratch_pool));
+ if (nb->skip)
+ return SVN_NO_ERROR;
+
+ /* Check for on-disk obstructions or missing files. */
+ SVN_ERR(svn_io_check_path(local_abspath, &kind_on_disk, scratch_pool));
+ if (kind_on_disk != svn_node_file)
+ {
+ SVN_ERR(update_local_add_notify_obstructed_or_missing(nb, svn_node_file,
+ kind_on_disk,
+ scratch_pool));
+ nb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(update_incoming_add_merge_props(&prop_state, &conflict_skel,
+ nb->local_relpath,
+ base_props, working_props,
+ b->db, b->wcroot,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_modified,
+ b->db, local_abspath,
+ FALSE /* exact_comparison */,
+ scratch_pool));
+ if (!is_modified)
+ {
+ svn_skel_t *work_item = NULL;
+
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, b->db,
+ local_abspath, NULL,
+ /* FIXME: use_commit_times? */
+ FALSE,
+ 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
+ {
+ const char *empty_file_abspath;
+ const char *pristine_abspath;
+ svn_skel_t *work_item = NULL;
+
+ /*
+ * Run a 3-way merge to update the file, using the empty file
+ * merge base, the post-update pristine text as the merge-left version,
+ * and the locally added content of the working file as the merge-right
+ * version.
+ */
+ SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_pristine_get_path(&pristine_abspath, b->db,
+ b->wcroot->abspath, base_checksum,
+ scratch_pool, scratch_pool));
+
+ /* Create a property diff which shows all props as added. */
+ SVN_ERR(svn_prop_diffs(&propchanges, working_props,
+ apr_hash_make(scratch_pool), scratch_pool));
+
+ SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel,
+ &merge_outcome, b->db,
+ empty_file_abspath,
+ pristine_abspath,
+ local_abspath,
+ local_abspath,
+ NULL, NULL, NULL, /* diff labels */
+ apr_hash_make(scratch_pool),
+ FALSE, /* dry-run */
+ NULL, /* diff3-cmd */
+ NULL, /* merge options */
+ propchanges,
+ b->cancel_func, b->cancel_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;
+ }
+
+ /* If there are any conflicts to be stored, convert them into work items
+ * too. */
+ if (conflict_skel)
+ {
+ svn_wc_conflict_version_t *new_version;
+ svn_node_kind_t new_kind;
+ svn_revnum_t new_rev;
+ const char *repos_relpath;
+
+ new_version = svn_wc_conflict_version_dup(nb->b->new_version,
+ scratch_pool);
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &new_kind, &new_rev,
+ &repos_relpath, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ b->wcroot, nb->local_relpath,
+ scratch_pool, scratch_pool));
+ /* Fill in conflict info templates with info for this node. */
+ new_version->path_in_repos = repos_relpath;
+ new_version->node_kind = new_kind;
+ new_version->peg_rev = new_rev;
+
+ /* Create conflict markers. */
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel, NULL,
+ new_version, scratch_pool,
+ scratch_pool));
+ if (prop_state == svn_wc_notify_state_conflicted)
+ SVN_ERR(svn_wc__conflict_create_markers(&work_items, b->db,
+ local_abspath,
+ conflict_skel,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ SVN_ERR(update_move_list_add(b->wcroot, nb->local_relpath, b->db,
+ svn_wc_notify_update_update,
+ svn_node_file, content_state, prop_state,
+ conflict_skel, work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_update_add_merge_dirprops(added_node_baton_t *nb,
+ apr_hash_t *working_props,
+ apr_hash_t *base_props,
+ apr_pool_t *scratch_pool)
+{
+ update_local_add_baton_t *b = nb->b;
+ svn_skel_t *conflict_skel = NULL;
+ svn_wc_notify_state_t prop_state;
+ svn_skel_t *work_items = NULL;
+ svn_node_kind_t kind_on_disk;
+ const char *local_abspath = svn_dirent_join(b->wcroot->abspath,
+ nb->local_relpath,
+ scratch_pool);
+
+ SVN_ERR(update_local_add_mark_node_edited(nb, scratch_pool));
+ if (nb->skip)
+ return SVN_NO_ERROR;
+
+ /* Check for on-disk obstructions or missing files. */
+ SVN_ERR(svn_io_check_path(local_abspath, &kind_on_disk, scratch_pool));
+ if (kind_on_disk != svn_node_dir)
+ {
+ SVN_ERR(update_local_add_notify_obstructed_or_missing(nb, svn_node_dir,
+ kind_on_disk,
+ scratch_pool));
+ nb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(update_incoming_add_merge_props(&prop_state, &conflict_skel,
+ nb->local_relpath,
+ base_props, working_props,
+ b->db, b->wcroot,
+ scratch_pool, scratch_pool));
+
+ /* If there are any conflicts to be stored, convert them into work items. */
+ if (conflict_skel && prop_state == svn_wc_notify_state_conflicted)
+ {
+ svn_wc_conflict_version_t *new_version;
+ svn_node_kind_t new_kind;
+ svn_revnum_t new_rev;
+ const char *repos_relpath;
+
+ new_version = svn_wc_conflict_version_dup(nb->b->new_version,
+ scratch_pool);
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &new_kind, &new_rev,
+ &repos_relpath, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ b->wcroot, nb->local_relpath,
+ scratch_pool, scratch_pool));
+ /* Fill in conflict info templates with info for this node. */
+ new_version->path_in_repos = repos_relpath;
+ new_version->node_kind = new_kind;
+ new_version->peg_rev = new_rev;
+
+ /* Create conflict markers. */
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel, NULL,
+ new_version, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_wc__conflict_create_markers(&work_items, b->db,
+ local_abspath,
+ conflict_skel,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ SVN_ERR(update_move_list_add(b->wcroot, nb->local_relpath, b->db,
+ svn_wc_notify_update_update, svn_node_dir,
+ svn_wc_notify_state_inapplicable, prop_state,
+ conflict_skel, work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_locally_added_node(added_node_baton_t *nb,
+ apr_pool_t *scratch_pool)
+{
+ update_local_add_baton_t *b = nb->b;
+ svn_wc__db_wcroot_t *wcroot = b->wcroot;
+ svn_wc__db_t *db = b->db;
+ svn_node_kind_t base_kind, working_kind;
+ const svn_checksum_t *base_checksum;
+ apr_hash_t *base_props, *working_props;
+ apr_array_header_t *base_children, *working_children;
+ const char *local_abspath = svn_dirent_join(wcroot->abspath,
+ nb->local_relpath,
+ scratch_pool);
+
+ if (b->cancel_func)
+ SVN_ERR(b->cancel_func(b->cancel_baton));
+
+ if (nb->skip)
+ return SVN_NO_ERROR;
+
+ /* Compare the tree conflict victim's BASE layer to the working layer. */
+ SVN_ERR(get_info(&base_props, &base_checksum, &base_children, &base_kind,
+ nb->local_relpath, 0, wcroot, scratch_pool, scratch_pool));
+ SVN_ERR(get_working_info(&working_props, NULL, &working_children,
+ &working_kind, nb->local_relpath, wcroot,
+ scratch_pool, scratch_pool));
+ if (working_kind == svn_node_none)
+ {
+ svn_node_kind_t kind_on_disk;
+ svn_skel_t *work_item = NULL;
+
+ /* Skip obstructed nodes. */
+ SVN_ERR(svn_io_check_path(local_abspath, &kind_on_disk,
+ scratch_pool));
+ if (kind_on_disk != base_kind && kind_on_disk != svn_node_none)
+ {
+ SVN_ERR(update_move_list_add(nb->b->wcroot, nb->local_relpath,
+ nb->b->db,
+ svn_wc_notify_skip,
+ base_kind,
+ svn_wc_notify_state_obstructed,
+ svn_wc_notify_state_inapplicable,
+ NULL, NULL, scratch_pool));
+ nb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* The working tree has no node here. The working copy of this node
+ * is currently not installed because the base tree is shadowed.
+ * Queue an installation of this node into the working copy. */
+ if (base_kind == svn_node_file || base_kind == svn_node_symlink)
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath,
+ NULL,
+ /* FIXME: use_commit_times? */
+ FALSE,
+ TRUE, /* record_file_info */
+ scratch_pool, scratch_pool));
+ else if (base_kind == svn_node_dir)
+ SVN_ERR(svn_wc__wq_build_dir_install(&work_item, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (work_item)
+ SVN_ERR(update_move_list_add(wcroot, nb->local_relpath, db,
+ svn_wc_notify_update_add,
+ base_kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable,
+ NULL, work_item, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ if (base_kind != working_kind)
+ {
+ if (working_kind == svn_node_file || working_kind == svn_node_symlink)
+ {
+ svn_checksum_t *working_checksum = NULL;
+
+ if (base_checksum)
+ SVN_ERR(svn_io_file_checksum2(&working_checksum, local_abspath,
+ base_checksum->kind, scratch_pool));
+ SVN_ERR(tc_editor_update_add_new_file(nb, base_kind, base_checksum,
+ base_props, working_kind,
+ working_checksum, working_props,
+ scratch_pool));
+ }
+ else if (working_kind == svn_node_dir)
+ SVN_ERR(tc_editor_update_add_new_directory(nb, base_kind, base_props,
+ working_props,
+ scratch_pool));
+ }
+ else
+ {
+ svn_boolean_t props_equal;
+
+ SVN_ERR(props_match(&props_equal, base_props, working_props,
+ scratch_pool));
+
+ if (working_kind == svn_node_file || working_kind == svn_node_symlink)
+ {
+ svn_checksum_t *working_checksum;
+
+ SVN_ERR_ASSERT(base_checksum);
+ SVN_ERR(svn_io_file_checksum2(&working_checksum, local_abspath,
+ base_checksum->kind, scratch_pool));
+ if (!props_equal || !svn_checksum_match(base_checksum,
+ working_checksum))
+ SVN_ERR(tc_editor_update_add_merge_files(nb, working_checksum,
+ base_checksum,
+ working_props, base_props,
+ scratch_pool));
+ }
+ else if (working_kind == svn_node_dir && !props_equal)
+ SVN_ERR(tc_editor_update_add_merge_dirprops(nb, working_props,
+ base_props,
+ scratch_pool));
+ }
+
+ if (nb->skip)
+ return SVN_NO_ERROR;
+
+ if (working_kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i = 0, j = 0;
+
+ while (i < base_children->nelts || j < working_children->nelts)
+ {
+ const char *child_name;
+ svn_boolean_t base_only = FALSE, working_only = FALSE;
+ added_node_baton_t cnb = { 0 };
+
+ cnb.pb = nb;
+ cnb.b = nb->b;
+ cnb.skip = FALSE;
+
+ svn_pool_clear(iterpool);
+ if (i >= base_children->nelts)
+ {
+ working_only = TRUE;
+ child_name = APR_ARRAY_IDX(working_children, j, const char *);
+ }
+ else if (j >= working_children->nelts)
+ {
+ base_only = TRUE;
+ child_name = APR_ARRAY_IDX(base_children, i, const char *);
+ }
+ else
+ {
+ const char *base_name = APR_ARRAY_IDX(base_children, i,
+ const char *);
+ const char *working_name = APR_ARRAY_IDX(working_children, j,
+ const char *);
+ int cmp = strcmp(base_name, working_name);
+
+ if (cmp > 0)
+ working_only = TRUE;
+ else if (cmp < 0)
+ base_only = TRUE;
+
+ child_name = working_only ? working_name : base_name;
+ }
+
+ cnb.local_relpath = svn_relpath_join(nb->local_relpath, child_name,
+ iterpool);
+
+ SVN_ERR(update_locally_added_node(&cnb, iterpool));
+
+ if (!working_only)
+ ++i;
+ if (!base_only)
+ ++j;
+
+ if (nb->skip) /* Does parent now want a skip? */
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_update_local_add(). */
+static svn_error_t *
+update_local_add(svn_revnum_t *new_rev,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ update_local_add_baton_t b = { 0 };
+ added_node_baton_t nb = { 0 };
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *repos_relpath;
+ apr_int64_t repos_id;
+ svn_node_kind_t new_kind;
+ svn_sqlite__stmt_t *stmt;
+
+ b.add_op_depth = relpath_depth(local_relpath); /* DST op-root */
+
+ SVN_ERR(verify_write_lock(wcroot, local_relpath, scratch_pool));
+
+ b.db = db;
+ b.wcroot = wcroot;
+ b.cancel_func = cancel_func;
+ b.cancel_baton = cancel_baton;
+
+ /* Read new version info from the updated BASE node. */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &new_kind, new_rev,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid, wcroot,
+ repos_id, scratch_pool));
+ b.new_version = svn_wc_conflict_version_create2(repos_root_url, repos_uuid,
+ repos_relpath, *new_rev,
+ new_kind, scratch_pool);
+
+ /* Create a new, and empty, list for notification information. */
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ /* Drive the editor... */
+ nb.b = &b;
+ nb.local_relpath = local_relpath;
+ nb.skip = FALSE;
+ SVN_ERR(update_locally_added_node(&nb, scratch_pool));
+
+ /* The conflict victim is now part of the base tree.
+ * Remove the locally added version of the conflict victim and its children.
+ * Any children we want to retain are at a higher op-depth so they won't
+ * be deleted by this statement. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORKING_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ relpath_depth(local_relpath)));
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+
+ /* Remove the tree conflict marker. */
+ SVN_ERR(svn_wc__db_op_mark_resolved_internal(wcroot, local_relpath, db,
+ FALSE, FALSE, TRUE,
+ NULL, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_update_local_add(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_revnum_t new_rev;
+ 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(update_local_add(&new_rev, db, wcroot,
+ local_relpath,
+ cancel_func, cancel_baton,
+ scratch_pool),
+ wcroot);
+
+ /* Send all queued up notifications. */
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, new_rev, new_rev,
+ 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_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
tree LOCAL_RELPATH at OP_DEPTH, to FALSE otherwise. */
static svn_error_t *