| /* |
| * 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 initial 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_sorts_private.h" |
| #include "private/svn_sqlite.h" |
| #include "private/svn_wc_private.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" |
| |
| /* Helper functions */ |
| /* Return the absolute path, in local path style, of LOCAL_RELPATH |
| in WCROOT. */ |
| static const char * |
| path_for_error_message(const svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| apr_pool_t *result_pool) |
| { |
| const char *local_abspath |
| = svn_dirent_join(wcroot->abspath, local_relpath, result_pool); |
| |
| return svn_dirent_local_style(local_abspath, result_pool); |
| } |
| |
| /* Ensure that there is a working copy lock for LOCAL_RELPATH in WCROOT */ |
| static svn_error_t * |
| verify_write_lock(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| apr_pool_t *scratch_pool) |
| { |
| svn_boolean_t locked; |
| |
| SVN_ERR(svn_wc__db_wclock_owns_lock_internal(&locked, wcroot, local_relpath, |
| FALSE, scratch_pool)); |
| if (!locked) |
| { |
| return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL, |
| _("No write-lock in '%s'"), |
| path_for_error_message(wcroot, local_relpath, |
| scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* In our merge conflicts we record the move_op_src path, which is essentially |
| the depth at which what was moved is marked deleted. The problem is that |
| this depth is not guaranteed to be stable, because somebody might just |
| remove another ancestor, or revert one. |
| |
| To work around this problem we locate the layer below this path, and use |
| that to pinpoint whatever is moved. |
| |
| For a path SRC_RELPATH that was deleted by an operation rooted at |
| DELETE_OP_DEPTH find the op-depth at which the node was originally added. |
| */ |
| static svn_error_t * |
| find_src_op_depth(int *src_op_depth, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *src_relpath, |
| int delete_op_depth, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_HIGHEST_WORKING_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, |
| src_relpath, delete_op_depth)); |
| |
| 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_PATH_UNEXPECTED_STATUS, NULL, |
| _("'%s' is not deleted"), |
| path_for_error_message(wcroot, src_relpath, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* |
| * 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. |
| */ |
| |
| typedef struct update_move_baton_t { |
| svn_wc__db_t *db; |
| svn_wc__db_wcroot_t *wcroot; |
| |
| int src_op_depth; |
| int dst_op_depth; |
| |
| svn_wc_operation_t operation; |
| svn_wc_conflict_version_t *old_version; |
| svn_wc_conflict_version_t *new_version; |
| |
| svn_cancel_func_t cancel_func; |
| void *cancel_baton; |
| } update_move_baton_t; |
| |
| /* Per node flags for tree conflict collection */ |
| typedef struct node_move_baton_t |
| { |
| svn_boolean_t skip; |
| svn_boolean_t shadowed; |
| svn_boolean_t edited; |
| |
| const char *src_relpath; |
| const char *dst_relpath; |
| |
| update_move_baton_t *umb; |
| struct node_move_baton_t *pb; |
| } node_move_baton_t; |
| |
| /* |
| * 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, and at the same time install |
| a conflict and/or work items. */ |
| static svn_error_t * |
| update_move_list_add(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| svn_wc__db_t *db, |
| 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_skel_t *conflict, |
| svn_skel_t *work_item, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| |
| if (conflict) |
| { |
| svn_boolean_t tree_conflict; |
| |
| SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, NULL, NULL, |
| &tree_conflict, |
| db, wcroot->abspath, conflict, |
| scratch_pool, scratch_pool)); |
| if (tree_conflict) |
| { |
| action = svn_wc_notify_tree_conflict; |
| content_state = svn_wc_notify_state_inapplicable; |
| prop_state = svn_wc_notify_state_inapplicable; |
| } |
| } |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_INSERT_UPDATE_MOVE_LIST)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "sdtdd", local_relpath, |
| action, kind_map_none, kind, |
| content_state, prop_state)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| |
| if (conflict) |
| SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, conflict, |
| scratch_pool)); |
| |
| if (work_item) |
| SVN_ERR(svn_wc__db_wq_add_internal(wcroot, work_item, scratch_pool)); |
| |
| 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_token(stmt, 2, kind_map_none); |
| 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; |
| } |
| |
| /* Create a tree-conflict for recording on LOCAL_RELPATH if such |
| a tree-conflict does not already exist. */ |
| static svn_error_t * |
| create_tree_conflict(svn_skel_t **conflict_p, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| const char *dst_op_root_relpath, |
| svn_wc__db_t *db, |
| const svn_wc_conflict_version_t *old_version, |
| const svn_wc_conflict_version_t *new_version, |
| 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 *result_pool, |
| 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) |
| { |
| const char *child_relpath = svn_relpath_skip_ancestor( |
| dst_op_root_relpath, |
| local_relpath); |
| SVN_ERR_ASSERT(child_relpath != NULL); |
| new_repos_relpath = svn_relpath_join(new_version->path_in_repos, |
| child_relpath, scratch_pool); |
| } |
| |
| err = svn_wc__db_read_conflict_internal(&conflict, NULL, NULL, |
| wcroot, local_relpath, |
| result_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_FOUND_CONFLICT, NULL, |
| _("'%s' already in conflict"), |
| path_for_error_message(wcroot, 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_FOUND_CONFLICT, NULL, |
| _("'%s' already in conflict"), |
| path_for_error_message(wcroot, |
| local_relpath, |
| scratch_pool)); |
| |
| /* Already a suitable tree-conflict. */ |
| *conflict_p = conflict; |
| return SVN_NO_ERROR; |
| } |
| } |
| else |
| conflict = svn_wc__conflict_skel_create(result_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, |
| 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); |
| |
| 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, |
| result_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, |
| result_pool, scratch_pool)); |
| } |
| |
| *conflict_p = conflict; |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| create_node_tree_conflict(svn_skel_t **conflict_p, |
| node_move_baton_t *nmb, |
| const char *dst_local_relpath, |
| svn_node_kind_t old_kind, |
| svn_node_kind_t new_kind, |
| svn_wc_conflict_reason_t reason, |
| svn_wc_conflict_action_t action, |
| const char *move_src_op_root_relpath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| 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, |
| scratch_pool); |
| |
| dst_repos_relpath = |
| svn_relpath_join(nmb->umb->old_version->path_in_repos, |
| svn_relpath_skip_ancestor(dst_root_relpath, |
| 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, |
| umb->dst_op_depth, |
| scratch_pool), |
| umb->db, |
| umb->old_version, umb->new_version, |
| umb->operation, old_kind, new_kind, |
| dst_repos_relpath, |
| reason, action, move_src_op_root_relpath, |
| result_pool, scratch_pool)); |
| } |
| |
| /* Checks if a specific local path is shadowed as seen from the move root. |
| Helper for update_moved_away_node() */ |
| static svn_error_t * |
| check_node_shadowed(svn_boolean_t *shadowed, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| int move_root_dst_op_depth, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_WORKING_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (have_row) |
| { |
| int op_depth = svn_sqlite__column_int(stmt, 0); |
| |
| *shadowed = (op_depth > move_root_dst_op_depth); |
| } |
| else |
| *shadowed = FALSE; |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Set a tree conflict for the shadowed node LOCAL_RELPATH, which is |
| the ROOT OF THE OBSTRUCTION if such a tree-conflict does not |
| already exist. KIND is the kind of the incoming LOCAL_RELPATH. */ |
| static svn_error_t * |
| mark_tc_on_op_root(node_move_baton_t *nmb, |
| svn_node_kind_t old_kind, |
| svn_node_kind_t new_kind, |
| svn_wc_conflict_action_t action, |
| apr_pool_t *scratch_pool) |
| { |
| update_move_baton_t *b = nmb->umb; |
| const char *move_dst_relpath; |
| svn_skel_t *conflict; |
| |
| SVN_ERR_ASSERT(nmb->shadowed && !nmb->pb->shadowed); |
| |
| nmb->skip = TRUE; |
| |
| if (old_kind == svn_node_none) |
| move_dst_relpath = NULL; |
| else |
| SVN_ERR(svn_wc__db_scan_moved_to_internal(NULL, &move_dst_relpath, NULL, |
| b->wcroot, nmb->dst_relpath, |
| b->dst_op_depth, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(create_node_tree_conflict(&conflict, nmb, nmb->dst_relpath, |
| old_kind, new_kind, |
| (move_dst_relpath |
| ? svn_wc_conflict_reason_moved_away |
| : svn_wc_conflict_reason_deleted), |
| action, move_dst_relpath |
| ? nmb->dst_relpath |
| : NULL, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(update_move_list_add(b->wcroot, nmb->dst_relpath, b->db, |
| svn_wc_notify_tree_conflict, |
| new_kind, |
| svn_wc_notify_state_inapplicable, |
| svn_wc_notify_state_inapplicable, |
| conflict, NULL, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| mark_node_edited(node_move_baton_t *nmb, |
| apr_pool_t *scratch_pool) |
| { |
| if (nmb->edited) |
| return SVN_NO_ERROR; |
| |
| if (nmb->pb) |
| { |
| SVN_ERR(mark_node_edited(nmb->pb, scratch_pool)); |
| |
| if (nmb->pb->skip) |
| nmb->skip = TRUE; |
| } |
| |
| nmb->edited = TRUE; |
| |
| if (nmb->skip) |
| return SVN_NO_ERROR; |
| |
| if (nmb->shadowed && !(nmb->pb && nmb->pb->shadowed)) |
| { |
| svn_node_kind_t dst_kind, src_kind; |
| |
| SVN_ERR(svn_wc__db_depth_get_info(NULL, &dst_kind, NULL, |
| NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| nmb->umb->wcroot, nmb->dst_relpath, |
| nmb->umb->dst_op_depth, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(svn_wc__db_depth_get_info(NULL, &src_kind, NULL, NULL, |
| NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| nmb->umb->wcroot, nmb->src_relpath, |
| nmb->umb->src_op_depth, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(mark_tc_on_op_root(nmb, |
| dst_kind, src_kind, |
| svn_wc_conflict_action_edit, |
| scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| mark_parent_edited(node_move_baton_t *nmb, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(nmb && nmb->pb); |
| |
| SVN_ERR(mark_node_edited(nmb->pb, scratch_pool)); |
| |
| if (nmb->pb->skip) |
| nmb->skip = TRUE; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| tc_editor_add_directory(node_move_baton_t *nmb, |
| const char *relpath, |
| svn_node_kind_t old_kind, |
| apr_hash_t *props, |
| apr_pool_t *scratch_pool) |
| { |
| update_move_baton_t *b = nmb->umb; |
| const char *local_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; |
| |
| if (nmb->shadowed) |
| { |
| svn_wc__db_status_t status; |
| |
| SVN_ERR(svn_wc__db_read_info_internal(&status, &wc_kind, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| b->wcroot, relpath, |
| scratch_pool, scratch_pool)); |
| |
| if (status == svn_wc__db_status_deleted) |
| reason = svn_wc_conflict_reason_deleted; |
| else if (status != svn_wc__db_status_added) |
| wc_kind = svn_node_none; |
| else if (old_kind == svn_node_none) |
| reason = svn_wc_conflict_reason_added; |
| else |
| reason = svn_wc_conflict_reason_replaced; |
| } |
| else |
| wc_kind = svn_node_none; |
| |
| local_abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool); |
| |
| if (wc_kind == svn_node_none) |
| { |
| /* Check for unversioned tree-conflict */ |
| SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool)); |
| } |
| |
| if (!nmb->shadowed && wc_kind == old_kind) |
| wc_kind = svn_node_none; /* Node will be gone once we install */ |
| |
| if (wc_kind != svn_node_none |
| && (nmb->shadowed || wc_kind != old_kind)) /* replace */ |
| { |
| SVN_ERR(create_node_tree_conflict(&conflict, nmb, 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(svn_wc__wq_build_dir_install(&work_item, b->db, local_abspath, |
| scratch_pool, scratch_pool)); |
| } |
| |
| SVN_ERR(update_move_list_add(b->wcroot, 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, |
| const svn_checksum_t *checksum, |
| apr_hash_t *props, |
| 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 *local_abspath; |
| svn_skel_t *work_item = NULL; |
| svn_skel_t *conflict = NULL; |
| |
| SVN_ERR(mark_parent_edited(nmb, scratch_pool)); |
| if (nmb->skip) |
| return SVN_NO_ERROR; |
| |
| if (nmb->shadowed) |
| { |
| svn_wc__db_status_t status; |
| |
| SVN_ERR(svn_wc__db_read_info_internal(&status, &wc_kind, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| b->wcroot, relpath, |
| scratch_pool, scratch_pool)); |
| |
| if (status == svn_wc__db_status_deleted) |
| reason = svn_wc_conflict_reason_deleted; |
| else if (status != svn_wc__db_status_added) |
| wc_kind = svn_node_none; |
| else if (old_kind == svn_node_none) |
| reason = svn_wc_conflict_reason_added; |
| else |
| reason = svn_wc_conflict_reason_replaced; |
| } |
| else |
| wc_kind = svn_node_none; |
| |
| local_abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool); |
| |
| if (wc_kind == svn_node_none) |
| { |
| /* Check for unversioned tree-conflict */ |
| SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool)); |
| } |
| |
| if (wc_kind != svn_node_none |
| && (nmb->shadowed || wc_kind != old_kind)) /* replace */ |
| { |
| SVN_ERR(create_node_tree_conflict(&conflict, nmb, 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; |
| } |
| else |
| { |
| /* 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(update_move_list_add(b->wcroot, 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_item, scratch_pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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, |
| svn_boolean_t set_operation, |
| 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 (set_operation) |
| { |
| 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, |
| update_move_baton_t *b, |
| const char *local_relpath, |
| 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_internal(actual_props, |
| b->wcroot, local_relpath, |
| 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, |
| b->db, svn_dirent_join(b->wcroot->abspath, |
| local_relpath, |
| scratch_pool), |
| 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_internal |
| relies on NODES row being updated via a different route . |
| |
| This extra property diff makes sure we clear the actual row when |
| the final result is unchanged properties. */ |
| 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. */ |
| SVN_ERR(svn_wc__db_op_set_props_internal(b->wcroot, local_relpath, |
| new_actual_props, |
| svn_wc__has_magic_property( |
| *propchanges), |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| tc_editor_alter_directory(node_move_baton_t *nmb, |
| const char *dst_relpath, |
| apr_hash_t *old_props, |
| apr_hash_t *new_props, |
| apr_pool_t *scratch_pool) |
| { |
| update_move_baton_t *b = nmb->umb; |
| working_node_version_t old_version, new_version; |
| svn_skel_t *work_items = NULL; |
| svn_skel_t *conflict_skel = NULL; |
| const char *local_abspath = svn_dirent_join(b->wcroot->abspath, dst_relpath, |
| scratch_pool); |
| svn_wc_notify_state_t prop_state; |
| apr_hash_t *actual_props; |
| apr_array_header_t *propchanges; |
| svn_node_kind_t wc_kind; |
| svn_boolean_t obstructed = FALSE; |
| |
| SVN_ERR(mark_node_edited(nmb, scratch_pool)); |
| if (nmb->skip) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool)); |
| if (wc_kind != svn_node_none && wc_kind != svn_node_dir) |
| { |
| SVN_ERR(create_node_tree_conflict(&conflict_skel, nmb, dst_relpath, |
| svn_node_dir, svn_node_dir, |
| svn_wc_conflict_reason_obstructed, |
| svn_wc_conflict_action_edit, |
| NULL, |
| scratch_pool, scratch_pool)); |
| obstructed = TRUE; |
| } |
| |
| old_version.location_and_kind = b->old_version; |
| new_version.location_and_kind = b->new_version; |
| |
| old_version.checksum = NULL; /* not a file */ |
| old_version.props = old_props; |
| new_version.checksum = NULL; /* not a file */ |
| new_version.props = new_props; |
| |
| SVN_ERR(update_working_props(&prop_state, &conflict_skel, |
| &propchanges, &actual_props, |
| b, dst_relpath, |
| &old_version, &new_version, |
| scratch_pool, scratch_pool)); |
| |
| if (prop_state == svn_wc_notify_state_conflicted) |
| { |
| const char *move_dst_repos_relpath; |
| |
| SVN_ERR(svn_wc__db_depth_get_info(NULL, NULL, NULL, |
| &move_dst_repos_relpath, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, |
| b->wcroot, dst_relpath, |
| b->dst_op_depth, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(create_conflict_markers(&work_items, local_abspath, |
| b->db, move_dst_repos_relpath, |
| conflict_skel, b->operation, |
| &old_version, &new_version, |
| svn_node_dir, !obstructed, |
| scratch_pool, scratch_pool)); |
| } |
| |
| SVN_ERR(update_move_list_add(b->wcroot, dst_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; |
| } |
| |
| /* Edit the file found at the move destination, which is initially at |
| * the old state. Merge the changes into the "working"/"actual" file. |
| * |
| * 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 * |
| tc_editor_alter_file(node_move_baton_t *nmb, |
| const char *dst_relpath, |
| const svn_checksum_t *old_checksum, |
| const svn_checksum_t *new_checksum, |
| apr_hash_t *old_props, |
| apr_hash_t *new_props, |
| apr_pool_t *scratch_pool) |
| { |
| update_move_baton_t *b = nmb->umb; |
| working_node_version_t old_version, new_version; |
| const char *local_abspath = svn_dirent_join(b->wcroot->abspath, |
| dst_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_node_kind_t wc_kind; |
| svn_boolean_t obstructed = FALSE; |
| |
| SVN_ERR(mark_node_edited(nmb, scratch_pool)); |
| if (nmb->skip) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool)); |
| if (wc_kind != svn_node_none && wc_kind != svn_node_file) |
| { |
| SVN_ERR(create_node_tree_conflict(&conflict_skel, nmb, dst_relpath, |
| svn_node_file, svn_node_file, |
| svn_wc_conflict_reason_obstructed, |
| svn_wc_conflict_action_edit, |
| NULL, |
| scratch_pool, scratch_pool)); |
| obstructed = TRUE; |
| } |
| |
| old_version.location_and_kind = b->old_version; |
| new_version.location_and_kind = b->new_version; |
| |
| old_version.checksum = old_checksum; |
| old_version.props = old_props; |
| new_version.checksum = new_checksum; |
| new_version.props = new_props; |
| |
| /* ### TODO: Only do this when there is no higher WORKING layer */ |
| SVN_ERR(update_working_props(&prop_state, &conflict_skel, &propchanges, |
| &actual_props, b, dst_relpath, |
| &old_version, &new_version, |
| scratch_pool, scratch_pool)); |
| |
| if (!obstructed |
| && !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, |
| b->db, local_abspath, |
| FALSE /* exact_comparison */, |
| scratch_pool)); |
| if (!is_locally_modified) |
| { |
| SVN_ERR(svn_wc__wq_build_file_install(&work_item, b->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, |
| b->db, b->wcroot->abspath, |
| old_version.checksum, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(svn_wc__db_pristine_get_path(&new_pristine_abspath, |
| b->db, b->wcroot->abspath, |
| new_version.checksum, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel, |
| &merge_outcome, b->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, |
| 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; |
| } |
| } |
| 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) |
| { |
| const char *move_dst_repos_relpath; |
| |
| SVN_ERR(svn_wc__db_depth_get_info(NULL, NULL, NULL, |
| &move_dst_repos_relpath, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, |
| b->wcroot, dst_relpath, |
| b->dst_op_depth, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(create_conflict_markers(&work_item, local_abspath, b->db, |
| move_dst_repos_relpath, conflict_skel, |
| b->operation, &old_version, &new_version, |
| svn_node_file, !obstructed, |
| 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_merge_local_file_change(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; |
| 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_node_kind_t dst_kind_on_disk; |
| svn_boolean_t obstructed = FALSE; |
| |
| SVN_ERR(mark_node_edited(nmb, scratch_pool)); |
| if (nmb->skip) |
| return SVN_NO_ERROR; |
| |
| 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) |
| { |
| SVN_ERR(create_node_tree_conflict(&conflict_skel, nmb, dst_relpath, |
| svn_node_file, svn_node_file, |
| svn_wc_conflict_reason_obstructed, |
| svn_wc_conflict_action_edit, |
| NULL, |
| scratch_pool, scratch_pool)); |
| obstructed = 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; |
| |
| SVN_ERR(update_working_props(&prop_state, &conflict_skel, &propchanges, |
| &actual_props, b, dst_relpath, |
| &old_version, &new_version, |
| scratch_pool, scratch_pool)); |
| |
| if (!obstructed && do_text_merge) |
| { |
| const char *old_pristine_abspath; |
| const char *src_abspath; |
| |
| /* |
| * 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-left version, and the current content of the |
| * working file at the pre-move location as the merge-right 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); |
| SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel, |
| &merge_outcome, b->db, |
| old_pristine_abspath, |
| src_abspath, |
| dst_abspath, |
| dst_abspath, |
| NULL, NULL, NULL, /* diff labels */ |
| 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; |
| } |
| 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) |
| { |
| const char *dst_repos_relpath; |
| |
| SVN_ERR(svn_wc__db_depth_get_info(NULL, NULL, NULL, |
| &dst_repos_relpath, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, |
| b->wcroot, dst_relpath, |
| b->dst_op_depth, |
| scratch_pool, scratch_pool)); |
| |
| 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, !obstructed, |
| 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, |
| 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 |
| { |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| const char *del_abspath; |
| svn_boolean_t have_row; |
| |
| /* Get all descendants of the node in reverse order (so children are |
| handled before their parents, but not strictly depth first) */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, |
| STMT_SELECT_DESCENDANTS_OP_DEPTH_RV)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath, |
| b->dst_op_depth)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| while (have_row) |
| { |
| svn_error_t *err; |
| svn_skel_t *work_item; |
| svn_node_kind_t del_kind; |
| |
| 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_internal(b->wcroot, 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)); |
| |
| if (old_kind == svn_node_dir) |
| SVN_ERR(svn_wc__wq_build_dir_remove(&work_items, b->db, |
| b->wcroot->abspath, local_abspath, |
| FALSE /* recursive */, |
| scratch_pool, iterpool)); |
| else |
| SVN_ERR(svn_wc__wq_build_file_remove(&work_items, b->db, |
| b->wcroot->abspath, local_abspath, |
| scratch_pool, iterpool)); |
| |
| svn_pool_destroy(iterpool); |
| } |
| |
| /* 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. |
| * |
| * 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. |
| */ |
| |
| /* 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, or only describes a delete of a lower op-depth. |
| *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) |
| { |
| svn_wc__db_status_t status; |
| const char *repos_relpath; |
| svn_node_kind_t db_kind; |
| svn_error_t *err; |
| |
| err = svn_wc__db_depth_get_info(&status, &db_kind, NULL, &repos_relpath, NULL, |
| NULL, NULL, NULL, NULL, checksum, NULL, |
| NULL, props, |
| wcroot, local_relpath, op_depth, |
| result_pool, scratch_pool); |
| |
| /* If there is no node at this depth, 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); |
| |
| 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_OP_DEPTH_CHILDREN_EXISTS)); |
| 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 *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; |
| } |
| |
| /* 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(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 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; |
| |
| if (b->cancel_func) |
| SVN_ERR(b->cancel_func(b->cancel_baton)); |
| |
| SVN_ERR(get_info(&src_props, &src_checksum, &src_children, &src_kind, |
| src_relpath, b->src_op_depth, |
| wcroot, scratch_pool, scratch_pool)); |
| |
| SVN_ERR(get_info(&dst_props, &dst_checksum, &dst_children, &dst_kind, |
| dst_relpath, b->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(tc_editor_delete(nmb, dst_relpath, dst_kind, src_kind, |
| scratch_pool)); |
| } |
| |
| if (nmb->skip) |
| return SVN_NO_ERROR; |
| |
| if (src_kind != svn_node_none && src_kind != dst_kind) |
| { |
| if (src_kind == svn_node_file || src_kind == svn_node_symlink) |
| { |
| SVN_ERR(tc_editor_add_file(nmb, dst_relpath, dst_kind, |
| src_checksum, src_props, scratch_pool)); |
| } |
| else if (src_kind == svn_node_dir) |
| { |
| SVN_ERR(tc_editor_add_directory(nmb, dst_relpath, dst_kind, |
| src_props, scratch_pool)); |
| } |
| } |
| else if (src_kind != svn_node_none) |
| { |
| svn_boolean_t props_equal; |
| |
| SVN_ERR(props_match(&props_equal, src_props, dst_props, scratch_pool)); |
| |
| if (src_kind == svn_node_file || src_kind == svn_node_symlink) |
| { |
| if (!props_equal || !svn_checksum_match(src_checksum, dst_checksum)) |
| SVN_ERR(tc_editor_alter_file(nmb, dst_relpath, |
| dst_checksum, src_checksum, |
| dst_props, src_props, scratch_pool)); |
| } |
| else if (src_kind == svn_node_dir) |
| { |
| if (!props_equal) |
| SVN_ERR(tc_editor_alter_directory(nmb, dst_relpath, |
| dst_props, src_props, |
| scratch_pool)); |
| } |
| } |
| |
| if (nmb->skip) |
| return SVN_NO_ERROR; |
| |
| 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; |
| svn_boolean_t src_only = FALSE, dst_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 >= 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; |
| } |
| |
| cnmb.src_relpath = svn_relpath_join(src_relpath, child_name, |
| iterpool); |
| cnmb.dst_relpath = svn_relpath_join(dst_relpath, child_name, |
| iterpool); |
| |
| if (!cnmb.shadowed) |
| SVN_ERR(check_node_shadowed(&cnmb.shadowed, wcroot, |
| cnmb.dst_relpath, b->dst_op_depth, |
| iterpool)); |
| |
| SVN_ERR(update_moved_away_node(&cnmb, wcroot, cnmb.src_relpath, |
| cnmb.dst_relpath, iterpool)); |
| |
| if (!dst_only) |
| ++i; |
| if (!src_only) |
| ++j; |
| |
| if (nmb->skip) /* Does parent now want a skip? */ |
| break; |
| } |
| } |
| |
| 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) |
| return svn_error_trace(svn_sqlite__reset(stmt)); |
| |
| revision = svn_sqlite__column_revnum(stmt, 4); |
| repos_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool); |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| 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"), |
| path_for_error_message(wcroot, local_relpath, |
| 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"), |
| path_for_error_message(wcroot, |
| local_relpath, |
| 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_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 *delete_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 }; |
| const char *src_relpath, *dst_relpath; |
| 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_ERR_ASSERT(svn_relpath_skip_ancestor(delete_relpath, local_relpath)); |
| |
| /* Construct editor baton. */ |
| |
| SVN_ERR(find_src_op_depth(&umb.src_op_depth, wcroot, |
| local_relpath, relpath_depth(delete_relpath), |
| scratch_pool)); |
| |
| SVN_ERR(svn_wc__db_scan_moved_to_internal(&src_relpath, &dst_relpath, NULL, |
| wcroot, local_relpath, |
| umb.src_op_depth, |
| scratch_pool, scratch_pool)); |
| |
| if (dst_relpath == NULL) |
| return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, |
| _("The node '%s' has not been moved away"), |
| path_for_error_message(wcroot, local_relpath, |
| scratch_pool)); |
| |
| umb.dst_op_depth = relpath_depth(dst_relpath); |
| |
| SVN_ERR(verify_write_lock(wcroot, src_relpath, scratch_pool)); |
| SVN_ERR(verify_write_lock(wcroot, dst_relpath, scratch_pool)); |
| |
| |
| SVN_ERR(svn_wc__db_depth_get_info(NULL, &new_version.node_kind, |
| &new_version.peg_rev, |
| &new_version.path_in_repos, &repos_id, |
| NULL, NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, |
| wcroot, src_relpath, umb.src_op_depth, |
| 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)); |
| |
| 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, dst_relpath, umb.dst_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; |
| |
| if (umb.src_op_depth == 0) |
| SVN_ERR(suitable_for_move(wcroot, src_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)); |
| |
| /* Drive the editor... */ |
| |
| nmb.umb = &umb; |
| nmb.src_relpath = src_relpath; |
| nmb.dst_relpath = dst_relpath; |
| /* nmb.shadowed = FALSE; */ |
| /* nmb.edited = FALSE; */ |
| /* nmb.skip_children = FALSE; */ |
| |
| /* 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(&nmb, wcroot, src_relpath, dst_relpath, |
| scratch_pool)); |
| |
| SVN_ERR(svn_wc__db_op_copy_layer_internal(wcroot, src_relpath, |
| umb.src_op_depth, |
| dst_relpath, NULL, NULL, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *delete_op_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 *delete_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); |
| |
| delete_relpath |
| = svn_dirent_skip_ancestor(wcroot->abspath, delete_op_abspath); |
| |
| SVN_WC__DB_WITH_TXN( |
| update_moved_away_conflict_victim( |
| &old_rev, &new_rev, |
| db, wcroot, local_relpath, delete_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; |
| } |
| |
| 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; |
| } |
| |
| /* ### Drive TC_EDITOR so as to ... |
| */ |
| static svn_error_t * |
| walk_local_changes(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 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; |
| |
| if (b->cancel_func) |
| SVN_ERR(b->cancel_func(b->cancel_baton)); |
| |
| SVN_ERR(get_working_info(&src_props, &src_checksum, &src_children, |
| &src_kind, src_relpath, wcroot, scratch_pool, |
| scratch_pool)); |
| |
| SVN_ERR(get_info(&dst_props, &dst_checksum, &dst_children, &dst_kind, |
| dst_relpath, b->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(tc_editor_delete(nmb, dst_relpath, dst_kind, src_kind, |
| scratch_pool)); |
| } |
| |
| if (nmb->skip) |
| return SVN_NO_ERROR; |
| |
| if (src_kind != svn_node_none && src_kind != dst_kind) |
| { |
| if (src_kind == svn_node_file || src_kind == svn_node_symlink) |
| { |
| SVN_ERR(tc_editor_add_file(nmb, dst_relpath, dst_kind, |
| src_checksum, src_props, |
| scratch_pool)); |
| } |
| else if (src_kind == svn_node_dir) |
| { |
| SVN_ERR(tc_editor_add_directory(nmb, dst_relpath, dst_kind, |
| src_props, scratch_pool)); |
| } |
| } |
| else if (src_kind != svn_node_none) |
| { |
| svn_boolean_t props_equal; |
| |
| SVN_ERR(props_match(&props_equal, src_props, dst_props, scratch_pool)); |
| |
| if (src_kind == svn_node_file || src_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, |
| src_relpath, |
| scratch_pool), |
| FALSE /* exact_comparison */, |
| scratch_pool)); |
| if (!props_equal || is_modified) |
| SVN_ERR(tc_editor_merge_local_file_change(nmb, dst_relpath, |
| src_relpath, |
| src_checksum, |
| dst_checksum, |
| dst_props, src_props, |
| is_modified, |
| scratch_pool)); |
| } |
| else if (src_kind == svn_node_dir) |
| { |
| if (!props_equal) |
| SVN_ERR(tc_editor_alter_directory(nmb, dst_relpath, |
| dst_props, src_props, |
| scratch_pool)); |
| } |
| } |
| |
| if (nmb->skip) |
| return SVN_NO_ERROR; |
| |
| 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; |
| svn_boolean_t src_only = FALSE, dst_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 >= 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; |
| } |
| |
| cnmb.src_relpath = svn_relpath_join(src_relpath, child_name, |
| iterpool); |
| cnmb.dst_relpath = svn_relpath_join(dst_relpath, child_name, |
| iterpool); |
| |
| if (!cnmb.shadowed) |
| SVN_ERR(check_node_shadowed(&cnmb.shadowed, wcroot, |
| cnmb.dst_relpath, b->dst_op_depth, |
| iterpool)); |
| |
| SVN_ERR(walk_local_changes(&cnmb, wcroot, cnmb.src_relpath, |
| cnmb.dst_relpath, iterpool)); |
| |
| if (!dst_only) |
| ++i; |
| if (!src_only) |
| ++j; |
| |
| if (nmb->skip) /* Does parent now want a skip? */ |
| break; |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* The body of svn_wc__db_merge_local_changes(). */ |
| static svn_error_t * |
| merge_local_changes(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_ERR_ASSERT(svn_relpath_skip_ancestor(dst_relpath, local_relpath) == NULL); |
| |
| /* In case of 'merge' the source is in the BASE tree (+ local mods) and the |
| * destination is a copied tree. For update/switch the source is a copied |
| * tree (copied from the pre-update BASE revision when the tree conflict |
| * was raised), and the destination is in the BASE tree. */ |
| if (operation == svn_wc_operation_merge) |
| { |
| umb.src_op_depth = 0; |
| umb.dst_op_depth = relpath_depth(dst_relpath); |
| } |
| else |
| { |
| umb.src_op_depth = relpath_depth(local_relpath); |
| umb.dst_op_depth = 0; |
| } |
| |
| SVN_ERR(verify_write_lock(wcroot, local_relpath, scratch_pool)); |
| SVN_ERR(verify_write_lock(wcroot, dst_relpath, scratch_pool)); |
| |
| SVN_ERR(svn_wc__db_depth_get_info(NULL, &new_version.node_kind, |
| &new_version.peg_rev, |
| &new_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(&new_version.repos_url, |
| &new_version.repos_uuid, |
| wcroot, repos_id, |
| scratch_pool)); |
| |
| 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, dst_relpath, umb.dst_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; |
| |
| if (umb.src_op_depth == 0) |
| SVN_ERR(suitable_for_move(wcroot, local_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)); |
| |
| /* 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 move source, comparing each node with the equivalent node at |
| * the move destination and applying any local changes to nodes at the move |
| destination. */ |
| SVN_ERR(walk_local_changes(&nmb, wcroot, local_relpath, dst_relpath, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__db_merge_local_changes(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(merge_local_changes(&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; |
| } |
| |
| /* 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 * |
| depth_sufficient_to_bump(svn_boolean_t *can_bump, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| int op_depth, |
| 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, op_depth)); |
| 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, "isd", wcroot->wc_id, |
| local_relpath, op_depth)); |
| break; |
| |
| case svn_depth_immediates: |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_HAS_GRANDCHILDREN)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, |
| local_relpath, op_depth)); |
| 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, |
| int src_op_depth, |
| 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; |
| svn_skel_t *conflict; |
| |
| /* Verify precondition: We are allowed to set a tree conflict here. */ |
| SVN_ERR(verify_write_lock(wcroot, move_src_root_relpath, scratch_pool)); |
| |
| /* Read new (post-update) information from the new move source BASE node. */ |
| SVN_ERR(svn_wc__db_depth_get_info(NULL, &new_kind, &new_rev, |
| &new_repos_relpath, &repos_id, |
| NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| wcroot, move_src_op_root_relpath, |
| src_op_depth, scratch_pool, scratch_pool)); |
| SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid, |
| wcroot, repos_id, scratch_pool)); |
| |
| /* Read old (pre-update) information from the move destination node. |
| |
| This potentially touches nodes that aren't locked by us, but that is not |
| a problem because we have a SQLite write lock here, and all sqlite |
| operations that affect move stability use a sqlite lock as well. |
| (And affecting the move itself requires a write lock on the node that |
| we do own the lock for: the move source) |
| */ |
| 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)); |
| |
| if (strcmp(move_src_root_relpath, move_src_op_root_relpath)) |
| { |
| /* We have information for the op-root, but need it for the node that |
| we are putting the tree conflict on. Luckily we know that we have |
| a clean BASE */ |
| |
| const char *rpath = svn_relpath_skip_ancestor(move_src_op_root_relpath, |
| move_src_root_relpath); |
| |
| old_repos_relpath = svn_relpath_join(old_repos_relpath, rpath, |
| scratch_pool); |
| new_repos_relpath = svn_relpath_join(new_repos_relpath, rpath, |
| 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(create_tree_conflict(&conflict, wcroot, move_src_root_relpath, |
| move_dst_op_root_relpath, |
| db, old_version, new_version, |
| 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, scratch_pool)); |
| |
| SVN_ERR(update_move_list_add(wcroot, move_src_root_relpath, db, |
| svn_wc_notify_tree_conflict, |
| new_kind, |
| svn_wc_notify_state_inapplicable, |
| svn_wc_notify_state_inapplicable, |
| conflict, NULL, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Checks if SRC_RELPATH is within BUMP_DEPTH from BUMP_ROOT. Sets |
| * *SKIP to TRUE if the node should be skipped, otherwise to FALSE. |
| * Sets *SRC_DEPTH to the remaining depth at SRC_RELPATH. |
| */ |
| static svn_error_t * |
| check_bump_layer(svn_boolean_t *skip, |
| svn_depth_t *src_depth, |
| const char *bump_root, |
| svn_depth_t bump_depth, |
| const char *src_relpath, |
| svn_node_kind_t src_kind, |
| apr_pool_t *scratch_pool) |
| { |
| const char *relpath; |
| |
| *skip = FALSE; |
| *src_depth = bump_depth; |
| |
| relpath = svn_relpath_skip_ancestor(bump_root, src_relpath); |
| |
| if (!relpath) |
| *skip = TRUE; |
| |
| if (bump_depth == svn_depth_infinity) |
| return SVN_NO_ERROR; |
| |
| if (relpath && *relpath == '\0') |
| return SVN_NO_ERROR; |
| |
| switch (bump_depth) |
| { |
| case svn_depth_empty: |
| *skip = TRUE; |
| break; |
| |
| case svn_depth_files: |
| if (src_kind != svn_node_file) |
| { |
| *skip = TRUE; |
| break; |
| } |
| /* Fallthrough */ |
| case svn_depth_immediates: |
| if (!relpath || relpath_depth(relpath) > 1) |
| *skip = TRUE; |
| |
| *src_depth = svn_depth_empty; |
| break; |
| default: |
| SVN_ERR_MALFUNCTION(); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* The guts of bump_moved_away: Determines if a move can be bumped to match |
| * the move origin and if so performs this bump. |
| */ |
| static svn_error_t * |
| bump_moved_layer(svn_boolean_t *recurse, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| int op_depth, |
| const char *src_relpath, |
| int src_del_depth, |
| svn_depth_t src_depth, |
| const char *dst_relpath, |
| svn_wc__db_t *db, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| svn_skel_t *conflict; |
| svn_boolean_t can_bump; |
| const char *src_root_relpath; |
| |
| SVN_ERR(verify_write_lock(wcroot, local_relpath, scratch_pool)); |
| |
| *recurse = FALSE; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_HAS_LAYER_BETWEEN)); |
| |
| SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id, local_relpath, |
| op_depth, src_del_depth)); |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (have_row) |
| return SVN_NO_ERROR; |
| |
| if (op_depth == 0) |
| SVN_ERR(depth_sufficient_to_bump(&can_bump, wcroot, src_relpath, |
| op_depth, 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; |
| |
| /* Are we allowed to bump */ |
| if (can_bump) |
| { |
| svn_boolean_t locked; |
| |
| SVN_ERR(svn_wc__db_wclock_owns_lock_internal(&locked, wcroot, |
| dst_relpath, |
| FALSE, scratch_pool)); |
| |
| if (!locked) |
| can_bump = FALSE; |
| } |
| |
| src_root_relpath = svn_relpath_prefix(src_relpath, src_del_depth, |
| scratch_pool); |
| |
| if (!can_bump) |
| { |
| SVN_ERR(bump_mark_tree_conflict(wcroot, src_relpath, op_depth, |
| src_root_relpath, dst_relpath, |
| db, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| SVN_ERR(svn_wc__db_read_conflict_internal(&conflict, NULL, NULL, |
| wcroot, src_root_relpath, |
| scratch_pool, scratch_pool)); |
| |
| /* ### TODO: check this is the right sort of tree-conflict? */ |
| if (!conflict) |
| { |
| /* ### TODO: verify moved_here? */ |
| |
| SVN_ERR(verify_write_lock(wcroot, src_relpath, scratch_pool)); |
| |
| SVN_ERR(svn_wc__db_op_copy_layer_internal(wcroot, |
| src_relpath, op_depth, |
| dst_relpath, NULL, NULL, |
| scratch_pool)); |
| |
| *recurse = TRUE; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Internal storage for bump_moved_away() */ |
| struct bump_pair_t |
| { |
| const char *src_relpath; |
| const char *dst_relpath; |
| int src_del_op_depth; |
| svn_node_kind_t src_kind; |
| }; |
| |
| /* Bump moves of LOCAL_RELPATH and all its descendants that were |
| originally below LOCAL_RELPATH at op-depth OP_DEPTH. |
| */ |
| static svn_error_t * |
| bump_moved_away(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| int op_depth, |
| svn_depth_t depth, |
| svn_wc__db_t *db, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| apr_pool_t *iterpool; |
| int i; |
| apr_array_header_t *pairs = apr_array_make(scratch_pool, 32, |
| sizeof(struct bump_pair_t*)); |
| |
| /* Build an array, as we can't execute the same Sqlite query recursively */ |
| 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) |
| { |
| struct bump_pair_t *bp = apr_pcalloc(scratch_pool, sizeof(*bp)); |
| |
| bp->src_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); |
| bp->dst_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool); |
| bp->src_del_op_depth = svn_sqlite__column_int(stmt, 2); |
| bp->src_kind = svn_sqlite__column_token(stmt, 3, kind_map); |
| |
| APR_ARRAY_PUSH(pairs, struct bump_pair_t *) = bp; |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| for (i = 0; i < pairs->nelts; i++) |
| { |
| struct bump_pair_t *bp = APR_ARRAY_IDX(pairs, i, struct bump_pair_t *); |
| svn_boolean_t skip; |
| svn_depth_t src_wc_depth; |
| |
| svn_pool_clear(iterpool); |
| |
| |
| SVN_ERR(check_bump_layer(&skip, &src_wc_depth, local_relpath, depth, |
| bp->src_relpath, bp->src_kind, iterpool)); |
| |
| if (!skip) |
| { |
| svn_boolean_t recurse; |
| |
| SVN_ERR(bump_moved_layer(&recurse, wcroot, |
| local_relpath, op_depth, |
| bp->src_relpath, bp->src_del_op_depth, |
| src_wc_depth, bp->dst_relpath, |
| db, iterpool)); |
| |
| if (recurse) |
| SVN_ERR(bump_moved_away(wcroot, bp->dst_relpath, |
| relpath_depth(bp->dst_relpath), |
| depth, db, iterpool)); |
| } |
| } |
| |
| 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) |
| { |
| SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, |
| STMT_CREATE_UPDATE_MOVE_LIST)); |
| |
| if (local_relpath[0] != '\0') |
| { |
| const char *move_dst_op_root_relpath; |
| const char *move_src_root_relpath, *delete_relpath; |
| svn_error_t *err; |
| |
| /* Is the root of the update moved away? (Impossible for the wcroot) */ |
| |
| err = svn_wc__db_scan_moved_to_internal(&move_src_root_relpath, |
| &move_dst_op_root_relpath, |
| &delete_relpath, |
| wcroot, local_relpath, |
| 0 /* BASE */, |
| scratch_pool, scratch_pool); |
| |
| if (err) |
| { |
| if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) |
| return svn_error_trace(err); |
| |
| svn_error_clear(err); |
| } |
| else if (move_src_root_relpath) |
| { |
| if (strcmp(move_src_root_relpath, local_relpath)) |
| { |
| /* An ancestor of the path that was updated is moved away. |
| |
| If we have a lock on that ancestor, we can mark a tree |
| conflict on it, if we don't we ignore this case. A future |
| update of the ancestor will handle this. */ |
| svn_boolean_t locked; |
| |
| SVN_ERR(svn_wc__db_wclock_owns_lock_internal( |
| &locked, wcroot, |
| move_src_root_relpath, |
| FALSE, scratch_pool)); |
| |
| if (locked) |
| { |
| SVN_ERR(bump_mark_tree_conflict(wcroot, |
| move_src_root_relpath, 0, |
| delete_relpath, |
| move_dst_op_root_relpath, |
| db, scratch_pool)); |
| } |
| return SVN_NO_ERROR; |
| } |
| } |
| } |
| |
| SVN_ERR(bump_moved_away(wcroot, local_relpath, 0, depth, db, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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 * |
| fetch_conflict_details(int *src_op_depth, |
| svn_wc_operation_t *operation, |
| svn_wc_conflict_action_t *action, |
| svn_wc_conflict_version_t **left_version, |
| svn_wc_conflict_version_t **right_version, |
| svn_wc__db_wcroot_t *wcroot, |
| svn_wc__db_t *db, |
| const char *local_relpath, |
| const svn_skel_t *conflict_skel, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const apr_array_header_t *locations; |
| svn_boolean_t text_conflicted; |
| svn_boolean_t prop_conflicted; |
| svn_boolean_t tree_conflicted; |
| const char *move_src_op_root_abspath; |
| svn_wc_conflict_reason_t reason; |
| const char *local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, |
| scratch_pool); |
| |
| if (!conflict_skel) |
| return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, |
| _("'%s' is not in conflict"), |
| path_for_error_message(wcroot, local_relpath, |
| scratch_pool)); |
| |
| SVN_ERR(svn_wc__conflict_read_info(operation, &locations, |
| &text_conflicted, &prop_conflicted, |
| &tree_conflicted, |
| db, local_abspath, |
| conflict_skel, result_pool, |
| scratch_pool)); |
| |
| if (text_conflicted || prop_conflicted || !tree_conflicted) |
| return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, |
| _("'%s' is not a valid tree-conflict victim"), |
| path_for_error_message(wcroot, local_relpath, |
| scratch_pool)); |
| |
| SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, |
| action, |
| &move_src_op_root_abspath, |
| db, local_abspath, |
| conflict_skel, result_pool, |
| scratch_pool)); |
| |
| if (reason == svn_wc_conflict_reason_moved_away) |
| return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, |
| _("'%s' is already a moved away tree-conflict"), |
| path_for_error_message(wcroot, local_relpath, |
| scratch_pool)); |
| |
| if (left_version) |
| { |
| if (locations && locations->nelts > 0) |
| *left_version = APR_ARRAY_IDX(locations, 0, |
| svn_wc_conflict_version_t *); |
| else |
| *left_version = NULL; |
| } |
| |
| if (right_version) |
| { |
| if (locations && locations->nelts > 1) |
| *right_version = APR_ARRAY_IDX(locations, 1, |
| svn_wc_conflict_version_t *); |
| else |
| *right_version = NULL; |
| } |
| |
| { |
| int del_depth = relpath_depth(local_relpath); |
| |
| if (move_src_op_root_abspath) |
| del_depth = relpath_depth( |
| svn_dirent_skip_ancestor(wcroot->abspath, |
| move_src_op_root_abspath)); |
| |
| SVN_ERR(find_src_op_depth(src_op_depth, wcroot, local_relpath, del_depth, |
| scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__db_op_raise_moved_away_internal( |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| int src_op_depth, |
| svn_wc__db_t *db, |
| svn_wc_operation_t operation, |
| svn_wc_conflict_action_t action, |
| const svn_wc_conflict_version_t *old_version, |
| const svn_wc_conflict_version_t *new_version, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| 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_MOVED_DESCENDANTS_SRC)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, |
| src_op_depth)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| while(have_row) |
| { |
| svn_error_t *err; |
| int delete_op_depth = svn_sqlite__column_int(stmt, 0); |
| const char *src_relpath = svn_sqlite__column_text(stmt, 1, NULL); |
| svn_node_kind_t src_kind = svn_sqlite__column_token(stmt, 2, kind_map); |
| const char *src_repos_relpath = svn_sqlite__column_text(stmt, 3, NULL); |
| const char *dst_relpath = svn_sqlite__column_text(stmt, 4, NULL); |
| svn_skel_t *conflict; |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR_ASSERT(src_repos_relpath != NULL); |
| |
| err = create_tree_conflict(&conflict, wcroot, src_relpath, dst_relpath, |
| db, old_version, new_version, operation, |
| src_kind /* ### old kind */, |
| src_kind /* ### new kind */, |
| src_repos_relpath, |
| svn_wc_conflict_reason_moved_away, |
| action, |
| svn_relpath_prefix(src_relpath, |
| delete_op_depth, |
| iterpool), |
| iterpool, iterpool); |
| |
| if (!err) |
| err = update_move_list_add(wcroot, src_relpath, db, |
| svn_wc_notify_tree_conflict, |
| src_kind, |
| svn_wc_notify_state_inapplicable, |
| svn_wc_notify_state_inapplicable, |
| conflict, NULL, scratch_pool); |
| |
| 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_op_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_action_t action; |
| svn_wc_conflict_version_t *left_version, *right_version; |
| int move_src_op_depth; |
| svn_skel_t *conflict; |
| |
| 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_TXN4( |
| svn_wc__db_read_conflict_internal(&conflict, NULL, NULL, |
| wcroot, local_relpath, |
| scratch_pool, scratch_pool), |
| fetch_conflict_details(&move_src_op_depth, |
| &operation, &action, |
| &left_version, &right_version, |
| wcroot, db, local_relpath, conflict, |
| scratch_pool, scratch_pool), |
| svn_wc__db_op_mark_resolved_internal(wcroot, local_relpath, db, |
| FALSE, FALSE, TRUE, |
| NULL, scratch_pool), |
| svn_wc__db_op_raise_moved_away_internal(wcroot, local_relpath, |
| move_src_op_depth, |
| db, operation, action, |
| left_version, right_version, |
| scratch_pool), |
| wcroot); |
| |
| /* These version numbers are valid for update/switch notifications |
| only! */ |
| SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, |
| (left_version |
| ? left_version->peg_rev |
| : SVN_INVALID_REVNUM), |
| (right_version |
| ? right_version->peg_rev |
| : SVN_INVALID_REVNUM), |
| notify_func, notify_baton, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| break_moved_away(svn_wc__db_wcroot_t *wcroot, |
| svn_wc__db_t *db, |
| const char *local_relpath, |
| int parent_src_op_depth, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| apr_pool_t *iterpool; |
| svn_error_t *err = NULL; |
| |
| 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_DESCENDANTS_SRC)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, |
| parent_src_op_depth)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| iterpool = svn_pool_create(scratch_pool); |
| while (have_row) |
| { |
| int src_op_depth = svn_sqlite__column_int(stmt, 0); |
| const char *src_relpath = svn_sqlite__column_text(stmt, 1, NULL); |
| svn_node_kind_t src_kind = svn_sqlite__column_token(stmt, 2, kind_map); |
| const char *dst_relpath = svn_sqlite__column_text(stmt, 4, NULL); |
| |
| svn_pool_clear(iterpool); |
| |
| err = verify_write_lock(wcroot, src_relpath, iterpool); |
| |
| if (!err) |
| err = verify_write_lock(wcroot, dst_relpath, iterpool); |
| |
| if (err) |
| break; |
| |
| err = svn_error_trace( |
| svn_wc__db_op_break_move_internal(wcroot, |
| src_relpath, src_op_depth, |
| dst_relpath, NULL, iterpool)); |
| |
| if (err) |
| break; |
| |
| err = svn_error_trace( |
| update_move_list_add(wcroot, src_relpath, db, |
| svn_wc_notify_move_broken, |
| src_kind, |
| svn_wc_notify_state_inapplicable, |
| svn_wc_notify_state_inapplicable, |
| NULL, NULL, scratch_pool)); |
| |
| if (err) |
| break; |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| svn_pool_destroy(iterpool); |
| |
| return svn_error_compose_create(err, svn_sqlite__reset(stmt)); |
| } |
| |
| svn_error_t * |
| svn_wc__db_op_break_moved_away(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *del_op_root_abspath, |
| svn_boolean_t mark_tc_resolved, |
| 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; |
| const char *del_relpath; |
| int src_op_depth; |
| |
| SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, |
| db, local_abspath, |
| scratch_pool, scratch_pool)); |
| VERIFY_USABLE_WCROOT(wcroot); |
| |
| if (del_op_root_abspath) |
| del_relpath = svn_dirent_skip_ancestor(wcroot->abspath, |
| del_op_root_abspath); |
| else |
| del_relpath = NULL; |
| |
| |
| SVN_WC__DB_WITH_TXN4( |
| find_src_op_depth(&src_op_depth, wcroot, local_relpath, |
| del_relpath ? relpath_depth(del_relpath) |
| : relpath_depth(local_relpath), |
| scratch_pool), |
| break_moved_away(wcroot, db, local_relpath, src_op_depth, |
| scratch_pool), |
| mark_tc_resolved |
| ? svn_wc__db_op_mark_resolved_internal(wcroot, local_relpath, db, |
| FALSE, FALSE, TRUE, |
| NULL, scratch_pool) |
| : SVN_NO_ERROR, |
| SVN_NO_ERROR, |
| 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; |
| } |