| /* |
| * 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; |
| } |