Modified: subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c URL: http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c?rev=1862754&r1=1862753&r2=1862754&view=diff ============================================================================== --- subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c (original) +++ subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c Mon Jul 8 15:19:03 2019 @@ -8632,10 +8632,10 @@ resolve_incoming_move_file_text_merge(sv if (operation == svn_wc_operation_update || operation == svn_wc_operation_switch) { - svn_stream_t *working_stream; + svn_stream_t *moved_to_stream; svn_stream_t *incoming_stream; - /* Create a temporary copy of the working file in repository-normal form. + /* Create a temporary copy of the moved file in repository-normal form. * Set up this temporary file to be automatically removed. */ err = svn_stream_open_unique(&incoming_stream, &incoming_abspath, wc_tmpdir, @@ -8644,19 +8644,31 @@ resolve_incoming_move_file_text_merge(sv if (err) goto unlock_wc; - err = svn_wc__translated_stream(&working_stream, ctx->wc_ctx, - merge_source_abspath, - merge_source_abspath, + err = svn_wc__translated_stream(&moved_to_stream, ctx->wc_ctx, + moved_to_abspath, + moved_to_abspath, SVN_WC_TRANSLATE_TO_NF, scratch_pool, scratch_pool); if (err) goto unlock_wc; - err = svn_stream_copy3(working_stream, incoming_stream, + err = svn_stream_copy3(moved_to_stream, incoming_stream, NULL, NULL, /* no cancellation */ scratch_pool); if (err) goto unlock_wc; + + /* Overwrite the moved file with the conflict victim's content. + * Incoming changes will be merged in from the temporary file created + * above. This is required to correctly make local changes show up as + * 'mine' during the three-way text merge between the ancestor file, + * the conflict victim ('mine'), and the moved file ('theirs') which + * was brought in by the update/switch operation and occupies the path + * of the merge target. */ + err = svn_io_copy_file(merge_source_abspath, moved_to_abspath, FALSE, + scratch_pool); + if (err) + goto unlock_wc; } else if (operation == svn_wc_operation_merge) { @@ -8997,53 +9009,60 @@ unlock_wc: return SVN_NO_ERROR; } -/* Implements conflict_option_resolve_func_t. */ +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by moving the locally moved + * directory to the incoming move target location, and then merging changes. */ static svn_error_t * -resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, - svn_client_conflict_t *conflict, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) +resolve_both_moved_dir_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { svn_client_conflict_option_id_t option_id; - const char *local_abspath; + const char *victim_abspath; + const char *local_moved_to_abspath; svn_wc_operation_t operation; const char *lock_abspath; svn_error_t *err; const char *repos_root_url; - const char *repos_uuid; const char *incoming_old_repos_relpath; svn_revnum_t incoming_old_pegrev; const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; - const char *victim_repos_relpath; - svn_revnum_t victim_peg_rev; - const char *moved_to_repos_relpath; - svn_revnum_t moved_to_peg_rev; - struct conflict_tree_incoming_delete_details *details; + const char *incoming_moved_repos_relpath; + struct conflict_tree_incoming_delete_details *incoming_details; apr_array_header_t *possible_moved_to_abspaths; - const char *moved_to_abspath; - svn_client__pathrev_t *yca_loc; - svn_opt_revision_t yca_opt_rev; + const char *incoming_moved_to_abspath; + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; svn_client__conflict_report_t *conflict_report; - svn_boolean_t is_copy; - svn_boolean_t is_modified; + const char *incoming_old_url; + const char *incoming_moved_url; + svn_opt_revision_t incoming_old_opt_rev; + svn_opt_revision_t incoming_moved_opt_rev; - local_abspath = svn_client_conflict_get_local_abspath(conflict); + victim_abspath = svn_client_conflict_get_local_abspath(conflict); operation = svn_client_conflict_get_operation(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->moves == NULL) + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, _("The specified conflict resolution option " "requires details for tree conflict at '%s' " + "to be fetched from the repository first."), - svn_dirent_local_style(local_abspath, + svn_dirent_local_style(victim_abspath, scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); option_id = svn_client_conflict_option_get_id(option); - SVN_ERR_ASSERT(option_id == - svn_client_conflict_option_incoming_move_dir_merge); + SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_dir_merge); - SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid, + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, conflict, scratch_pool, scratch_pool)); SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( @@ -9055,186 +9074,78 @@ resolve_incoming_move_dir_merge(svn_clie NULL, conflict, scratch_pool, scratch_pool)); - /* Get repository location of the moved-away node (the conflict victim). */ - if (operation == svn_wc_operation_update || - operation == svn_wc_operation_switch) - { - victim_repos_relpath = incoming_old_repos_relpath; - victim_peg_rev = incoming_old_pegrev; - } - else if (operation == svn_wc_operation_merge) - SVN_ERR(svn_wc__node_get_repos_info(&victim_peg_rev, &victim_repos_relpath, - NULL, NULL, ctx->wc_ctx, local_abspath, - scratch_pool, scratch_pool)); - - /* Get repository location of the moved-here node (incoming move). */ possible_moved_to_abspaths = - svn_hash_gets(details->wc_move_targets, - get_moved_to_repos_relpath(details, scratch_pool)); - moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths, - details->wc_move_target_idx, - const char *); + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); - /* ### The following WC modifications should be atomic. */ + local_details = conflict->tree_conflict_local_details; + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); + /* ### The following WC modifications should be atomic. */ SVN_ERR(svn_wc__acquire_write_lock_for_resolve( &lock_abspath, ctx->wc_ctx, - svn_dirent_get_longest_ancestor(local_abspath, - moved_to_abspath, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, scratch_pool), scratch_pool, scratch_pool)); - err = svn_wc__node_get_origin(&is_copy, &moved_to_peg_rev, - &moved_to_repos_relpath, - NULL, NULL, NULL, NULL, - ctx->wc_ctx, moved_to_abspath, FALSE, - scratch_pool, scratch_pool); + /* Perform the merge. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + + incoming_moved_repos_relpath = + get_moved_to_repos_relpath(incoming_details, scratch_pool); + incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_moved_repos_relpath, SVN_VA_NULL); + incoming_moved_opt_rev.kind = svn_opt_revision_number; + incoming_moved_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + incoming_moved_url, &incoming_moved_opt_rev, + local_moved_to_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); if (err) goto unlock_wc; - if (!is_copy && operation == svn_wc_operation_merge) - { - err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Cannot resolve tree conflict on '%s' " - "(expected a copied item at '%s', but the " - "item is not a copy)"), - svn_dirent_local_style(local_abspath, - scratch_pool), - svn_dirent_local_style(moved_to_abspath, - scratch_pool)); - goto unlock_wc; - } - - if (moved_to_repos_relpath == NULL || moved_to_peg_rev == SVN_INVALID_REVNUM) - { - err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Cannot resolve tree conflict on '%s' " - "(could not determine origin of '%s')"), - svn_dirent_local_style(local_abspath, - scratch_pool), - svn_dirent_local_style(moved_to_abspath, - scratch_pool)); - goto unlock_wc; - } - /* Now find the youngest common ancestor of these nodes. */ - err = find_yca(&yca_loc, victim_repos_relpath, victim_peg_rev, - moved_to_repos_relpath, moved_to_peg_rev, - repos_root_url, repos_uuid, - NULL, ctx, scratch_pool, scratch_pool); + /* Revert local addition of the incoming move's target. */ + err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, + svn_depth_infinity, FALSE, NULL, TRUE, FALSE, + FALSE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); if (err) goto unlock_wc; - if (yca_loc == NULL) - { - err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Cannot resolve tree conflict on '%s' " - "(could not find common ancestor of '^/%s@%ld' " - " and '^/%s@%ld')"), - svn_dirent_local_style(local_abspath, - scratch_pool), - victim_repos_relpath, victim_peg_rev, - moved_to_repos_relpath, moved_to_peg_rev); - goto unlock_wc; - } - - yca_opt_rev.kind = svn_opt_revision_number; - yca_opt_rev.value.number = yca_loc->rev; - - err = verify_local_state_for_incoming_delete(conflict, option, ctx, - scratch_pool); + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); if (err) goto unlock_wc; - if (operation == svn_wc_operation_merge) - { - const char *move_target_url; - svn_opt_revision_t incoming_new_opt_rev; - - /* Revert the incoming move target directory. */ - SVN_ERR(svn_wc_revert6(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity, - FALSE, NULL, TRUE, FALSE, - TRUE /*added_keep_local*/, - NULL, NULL, /* no cancellation */ - ctx->notify_func2, ctx->notify_baton2, - scratch_pool)); - - /* The move operation is not part of natural history. We must replicate - * this move in our history. Record a move in the working copy. */ - err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath, - FALSE, /* this is not a meta-data only move */ - TRUE, /* allow mixed-revisions just in case */ - NULL, NULL, /* don't allow user to cancel here */ - ctx->notify_func2, ctx->notify_baton2, - scratch_pool); - if (err) - goto unlock_wc; - - /* Merge YCA_URL@YCA_REV->MOVE_TARGET_URL@MERGE_RIGHT into move target. */ - move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/", - get_moved_to_repos_relpath(details, - scratch_pool), - SVN_VA_NULL); - incoming_new_opt_rev.kind = svn_opt_revision_number; - incoming_new_opt_rev.value.number = incoming_new_pegrev; - err = svn_client__merge_locked(&conflict_report, - yca_loc->url, &yca_opt_rev, - move_target_url, &incoming_new_opt_rev, - moved_to_abspath, svn_depth_infinity, - TRUE, TRUE, /* do a no-ancestry merge */ - FALSE, FALSE, FALSE, - TRUE, /* Allow mixed-rev just in case, - * since conflict victims can't be - * updated to straighten out - * mixed-rev trees. */ - NULL, ctx, scratch_pool, scratch_pool); - if (err) - goto unlock_wc; - } - else - { - SVN_ERR_ASSERT(operation == svn_wc_operation_update || - operation == svn_wc_operation_switch); - - /* Merge local modifications into the incoming move target dir. */ - err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath, - TRUE, ctx->cancel_func, ctx->cancel_baton, - scratch_pool); - if (err) - goto unlock_wc; - - if (is_modified) - { - err = svn_wc__conflict_tree_update_incoming_move(ctx->wc_ctx, - local_abspath, - moved_to_abspath, - ctx->cancel_func, - ctx->cancel_baton, - ctx->notify_func2, - ctx->notify_baton2, - scratch_pool); - if (err) - goto unlock_wc; - } - - /* The move operation is part of our natural history. - * Delete the tree conflict victim (clears the tree conflict marker). */ - err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE, - NULL, NULL, /* don't allow user to cancel here */ - NULL, NULL, /* no extra notification */ - scratch_pool); - if (err) - goto unlock_wc; - } - if (ctx->notify_func2) { svn_wc_notify_t *notify; - notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree, + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, scratch_pool); ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); } + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + conflict->resolution_tree = option_id; unlock_wc: @@ -9247,14 +9158,19 @@ unlock_wc: } /* Implements conflict_option_resolve_func_t. - * Handles svn_client_conflict_option_local_move_file_text_merge - * and svn_client_conflict_option_sibling_move_file_text_merge. */ + * Resolve an incoming move vs local move conflict by merging from the + * incoming move's target location to the local move's target location, + * overriding the incoming move. */ static svn_error_t * -resolve_local_move_file_merge(svn_client_conflict_option_t *option, - svn_client_conflict_t *conflict, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) +resolve_both_moved_dir_move_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; const char *lock_abspath; svn_error_t *err; const char *repos_root_url; @@ -9262,29 +9178,39 @@ resolve_local_move_file_merge(svn_client svn_revnum_t incoming_old_pegrev; const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; - const char *wc_tmpdir; - const char *ancestor_tmp_abspath; - const char *incoming_tmp_abspath; - apr_hash_t *ancestor_props; - apr_hash_t *incoming_props; - svn_stream_t *stream; - const char *url; - const char *corrected_url; - const char *old_session_url; - svn_ra_session_t *ra_session; - svn_wc_merge_outcome_t merge_content_outcome; - svn_wc_notify_state_t merge_props_outcome; - apr_array_header_t *propdiffs; - struct conflict_tree_local_missing_details *details; - const char *merge_target_abspath; - const char *wcroot_abspath; - - SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, - conflict->local_abspath, scratch_pool, - scratch_pool)); + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; + svn_client__conflict_report_t *conflict_report; + const char *incoming_old_url; + const char *incoming_moved_url; + svn_opt_revision_t incoming_old_opt_rev; + svn_opt_revision_t incoming_moved_opt_rev; - details = conflict->tree_conflict_local_details; + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == + svn_client_conflict_option_both_moved_dir_move_merge); + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, conflict, scratch_pool, scratch_pool)); @@ -9297,162 +9223,149 @@ resolve_local_move_file_merge(svn_client NULL, conflict, scratch_pool, scratch_pool)); - if (details->wc_siblings) - { - merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, - details->preferred_sibling_idx, - const char *); - } - else if (details->wc_move_targets && details->move_target_repos_relpath) - { - apr_array_header_t *moves; - moves = svn_hash_gets(details->wc_move_targets, - details->move_target_repos_relpath); - merge_target_abspath = APR_ARRAY_IDX(moves, details->wc_move_target_idx, - const char *); - } - else - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Corresponding working copy node not found " - "for '%s'"), - svn_dirent_local_style( - svn_dirent_skip_ancestor( - wcroot_abspath, conflict->local_abspath), - scratch_pool)); - - SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, - merge_target_abspath, - scratch_pool, scratch_pool)); - - /* Fetch the common ancestor file's content. */ - SVN_ERR(svn_stream_open_unique(&stream, &ancestor_tmp_abspath, wc_tmpdir, - svn_io_file_del_on_pool_cleanup, - scratch_pool, scratch_pool)); - url = svn_path_url_add_component2(repos_root_url, - incoming_old_repos_relpath, - scratch_pool); - SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, - url, NULL, NULL, - FALSE, FALSE, ctx, - scratch_pool, scratch_pool)); - SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, stream, NULL, - &ancestor_props, scratch_pool)); - filter_props(ancestor_props, scratch_pool); - - /* Close stream to flush the file to disk. */ - SVN_ERR(svn_stream_close(stream)); - - /* Do the same for the incoming file's content. */ - SVN_ERR(svn_stream_open_unique(&stream, &incoming_tmp_abspath, wc_tmpdir, - svn_io_file_del_on_pool_cleanup, - scratch_pool, scratch_pool)); - url = svn_path_url_add_component2(repos_root_url, - incoming_new_repos_relpath, - scratch_pool); - SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, - url, scratch_pool)); - SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, stream, NULL, - &incoming_props, scratch_pool)); - /* Close stream to flush the file to disk. */ - SVN_ERR(svn_stream_close(stream)); - - filter_props(incoming_props, scratch_pool); + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); - /* Create a property diff for the files. */ - SVN_ERR(svn_prop_diffs(&propdiffs, incoming_props, ancestor_props, - scratch_pool)); + local_details = conflict->tree_conflict_local_details; + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); /* ### The following WC modifications should be atomic. */ SVN_ERR(svn_wc__acquire_write_lock_for_resolve( &lock_abspath, ctx->wc_ctx, - svn_dirent_get_longest_ancestor(conflict->local_abspath, - merge_target_abspath, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, scratch_pool), scratch_pool, scratch_pool)); - /* Perform the file merge. */ - err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, - ctx->wc_ctx, - ancestor_tmp_abspath, incoming_tmp_abspath, - merge_target_abspath, - NULL, NULL, NULL, /* labels */ - NULL, NULL, /* conflict versions */ - FALSE, /* dry run */ - NULL, NULL, /* diff3_cmd, merge_options */ - apr_hash_count(ancestor_props) ? ancestor_props : NULL, - propdiffs, - NULL, NULL, /* conflict func/baton */ + /* Revert the incoming move target directory. */ + err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, + svn_depth_infinity, + FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + /* The move operation is not part of natural history. We must replicate + * this move in our history. Record a move in the working copy. */ + err = svn_wc__move2(ctx->wc_ctx, local_moved_to_abspath, + incoming_moved_to_abspath, + FALSE, /* this is not a meta-data only move */ + TRUE, /* allow mixed-revisions just in case */ NULL, NULL, /* don't allow user to cancel here */ + ctx->notify_func2, ctx->notify_baton2, scratch_pool); - svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); if (err) - return svn_error_compose_create(err, - svn_wc__release_write_lock(ctx->wc_ctx, - lock_abspath, - scratch_pool)); + goto unlock_wc; - err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath, - scratch_pool); - err = svn_error_compose_create(err, - svn_wc__release_write_lock(ctx->wc_ctx, - lock_abspath, - scratch_pool)); + /* Merge INCOMING_OLD_URL@MERGE_LEFT->INCOMING_MOVED_URL@MERGE_RIGHT + * into the locally moved merge target. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + + incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_details->move_target_repos_relpath, + SVN_VA_NULL); + incoming_moved_opt_rev.kind = svn_opt_revision_number; + incoming_moved_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + incoming_moved_url, &incoming_moved_opt_rev, + incoming_moved_to_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); if (err) - return svn_error_trace(err); + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; if (ctx->notify_func2) { svn_wc_notify_t *notify; - /* Tell the world about the file merge that just happened. */ - notify = svn_wc_create_notify(merge_target_abspath, - svn_wc_notify_update_update, - scratch_pool); - if (merge_content_outcome == svn_wc_merge_conflict) - notify->content_state = svn_wc_notify_state_conflicted; - else - notify->content_state = svn_wc_notify_state_merged; - notify->prop_state = merge_props_outcome; - notify->kind = svn_node_file; - ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); - - /* And also about the successfully resolved tree conflict. */ - notify = svn_wc_create_notify(conflict->local_abspath, - svn_wc_notify_resolved_tree, + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, scratch_pool); ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); } - conflict->resolution_tree = svn_client_conflict_option_get_id(option); + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); return SVN_NO_ERROR; } /* Implements conflict_option_resolve_func_t. */ static svn_error_t * -resolve_local_move_dir_merge(svn_client_conflict_option_t *option, - svn_client_conflict_t *conflict, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) +resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { + svn_client_conflict_option_id_t option_id; + const char *local_abspath; + svn_wc_operation_t operation; const char *lock_abspath; svn_error_t *err; const char *repos_root_url; + const char *repos_uuid; const char *incoming_old_repos_relpath; svn_revnum_t incoming_old_pegrev; const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; - struct conflict_tree_local_missing_details *details; - const char *merge_target_abspath; + const char *victim_repos_relpath; + svn_revnum_t victim_peg_rev; + const char *moved_to_repos_relpath; + svn_revnum_t moved_to_peg_rev; + struct conflict_tree_incoming_delete_details *details; + apr_array_header_t *possible_moved_to_abspaths; + const char *moved_to_abspath; const char *incoming_old_url; - const char *incoming_new_url; svn_opt_revision_t incoming_old_opt_rev; - svn_opt_revision_t incoming_new_opt_rev; svn_client__conflict_report_t *conflict_report; + svn_boolean_t is_copy; + svn_boolean_t is_modified; - details = conflict->tree_conflict_local_details; + local_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first."), + svn_dirent_local_style(local_abspath, + scratch_pool)); - SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == + svn_client_conflict_option_incoming_move_dir_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid, conflict, scratch_pool, scratch_pool)); SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( @@ -9464,122 +9377,514 @@ resolve_local_move_dir_merge(svn_client_ NULL, conflict, scratch_pool, scratch_pool)); - if (details->wc_move_targets) + /* Get repository location of the moved-away node (the conflict victim). */ + if (operation == svn_wc_operation_update || + operation == svn_wc_operation_switch) { - apr_array_header_t *moves; - - moves = svn_hash_gets(details->wc_move_targets, - details->move_target_repos_relpath); - merge_target_abspath = - APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *); + victim_repos_relpath = incoming_old_repos_relpath; + victim_peg_rev = incoming_old_pegrev; } - else - merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, - details->preferred_sibling_idx, - const char *); + else if (operation == svn_wc_operation_merge) + SVN_ERR(svn_wc__node_get_repos_info(&victim_peg_rev, &victim_repos_relpath, + NULL, NULL, ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + + /* Get repository location of the moved-here node (incoming move). */ + possible_moved_to_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths, + details->wc_move_target_idx, + const char *); /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( &lock_abspath, ctx->wc_ctx, - svn_dirent_get_longest_ancestor(conflict->local_abspath, - merge_target_abspath, + svn_dirent_get_longest_ancestor(local_abspath, + moved_to_abspath, scratch_pool), scratch_pool, scratch_pool)); - /* Resolve to current working copy state. - * svn_client__merge_locked() requires this. */ - err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath, - scratch_pool); + err = svn_wc__node_get_origin(&is_copy, &moved_to_peg_rev, + &moved_to_repos_relpath, + NULL, NULL, NULL, NULL, + ctx->wc_ctx, moved_to_abspath, FALSE, + scratch_pool, scratch_pool); if (err) goto unlock_wc; + if (!is_copy && operation == svn_wc_operation_merge) + { + err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Cannot resolve tree conflict on '%s' " + "(expected a copied item at '%s', but the " + "item is not a copy)"), + svn_dirent_local_style(local_abspath, + scratch_pool), + svn_dirent_local_style(moved_to_abspath, + scratch_pool)); + goto unlock_wc; + } - /* Merge outstanding changes to the merge target. */ - incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", - incoming_old_repos_relpath, SVN_VA_NULL); - incoming_old_opt_rev.kind = svn_opt_revision_number; - incoming_old_opt_rev.value.number = incoming_old_pegrev; - incoming_new_url = apr_pstrcat(scratch_pool, repos_root_url, "/", - incoming_new_repos_relpath, SVN_VA_NULL); - incoming_new_opt_rev.kind = svn_opt_revision_number; - incoming_new_opt_rev.value.number = incoming_new_pegrev; - err = svn_client__merge_locked(&conflict_report, - incoming_old_url, &incoming_old_opt_rev, - incoming_new_url, &incoming_new_opt_rev, - merge_target_abspath, svn_depth_infinity, - TRUE, TRUE, /* do a no-ancestry merge */ - FALSE, FALSE, FALSE, - TRUE, /* Allow mixed-rev just in case, - * since conflict victims can't be - * updated to straighten out - * mixed-rev trees. */ - NULL, ctx, scratch_pool, scratch_pool); -unlock_wc: - svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); - err = svn_error_compose_create(err, - svn_wc__release_write_lock(ctx->wc_ctx, - lock_abspath, - scratch_pool)); + if (moved_to_repos_relpath == NULL || moved_to_peg_rev == SVN_INVALID_REVNUM) + { + err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Cannot resolve tree conflict on '%s' " + "(could not determine origin of '%s')"), + svn_dirent_local_style(local_abspath, + scratch_pool), + svn_dirent_local_style(moved_to_abspath, + scratch_pool)); + goto unlock_wc; + } + + err = verify_local_state_for_incoming_delete(conflict, option, ctx, + scratch_pool); if (err) - return svn_error_trace(err); + goto unlock_wc; + + if (operation == svn_wc_operation_merge) + { + const char *move_target_url; + svn_opt_revision_t incoming_new_opt_rev; + + /* Revert the incoming move target directory. */ + err = svn_wc_revert6(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity, + FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + /* The move operation is not part of natural history. We must replicate + * this move in our history. Record a move in the working copy. */ + err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath, + FALSE, /* this is not a meta-data only move */ + TRUE, /* allow mixed-revisions just in case */ + NULL, NULL, /* don't allow user to cancel here */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + /* Merge INCOMING_OLD_URL@MERGE_LEFT->MOVE_TARGET_URL@MERGE_RIGHT + * into move target. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + get_moved_to_repos_relpath(details, + scratch_pool), + SVN_VA_NULL); + incoming_new_opt_rev.kind = svn_opt_revision_number; + incoming_new_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + move_target_url, &incoming_new_opt_rev, + moved_to_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + } + else + { + SVN_ERR_ASSERT(operation == svn_wc_operation_update || + operation == svn_wc_operation_switch); + + /* Merge local modifications into the incoming move target dir. */ + err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath, + TRUE, ctx->cancel_func, ctx->cancel_baton, + scratch_pool); + if (err) + goto unlock_wc; + + if (is_modified) + { + err = svn_wc__conflict_tree_update_incoming_move(ctx->wc_ctx, + local_abspath, + moved_to_abspath, + ctx->cancel_func, + ctx->cancel_baton, + ctx->notify_func2, + ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + } + + /* The move operation is part of our natural history. + * Delete the tree conflict victim (clears the tree conflict marker). */ + err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE, + NULL, NULL, /* don't allow user to cancel here */ + NULL, NULL, /* no extra notification */ + scratch_pool); + if (err) + goto unlock_wc; + } if (ctx->notify_func2) { svn_wc_notify_t *notify; - /* Tell the world about the file merge that just happened. */ - notify = svn_wc_create_notify(merge_target_abspath, - svn_wc_notify_update_update, - scratch_pool); - if (conflict_report) - notify->content_state = svn_wc_notify_state_conflicted; - else - notify->content_state = svn_wc_notify_state_merged; - notify->kind = svn_node_dir; - ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); - - /* And also about the successfully resolved tree conflict. */ - notify = svn_wc_create_notify(conflict->local_abspath, - svn_wc_notify_resolved_tree, + notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree, scratch_pool); ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); } - conflict->resolution_tree = svn_client_conflict_option_get_id(option); + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); return SVN_NO_ERROR; } +/* Implements conflict_option_resolve_func_t. + * Handles svn_client_conflict_option_local_move_file_text_merge + * and svn_client_conflict_option_sibling_move_file_text_merge. */ static svn_error_t * -assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) +resolve_local_move_file_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { - svn_boolean_t text_conflicted; - - SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, NULL, NULL, - conflict, scratch_pool, - scratch_pool)); - - SVN_ERR_ASSERT(text_conflicted); /* ### return proper error? */ + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *wc_tmpdir; + const char *ancestor_tmp_abspath; + const char *incoming_tmp_abspath; + apr_hash_t *ancestor_props; + apr_hash_t *incoming_props; + svn_stream_t *stream; + const char *url; + const char *corrected_url; + const char *old_session_url; + svn_ra_session_t *ra_session; + svn_wc_merge_outcome_t merge_content_outcome; + svn_wc_notify_state_t merge_props_outcome; + apr_array_header_t *propdiffs; + struct conflict_tree_local_missing_details *details; + const char *merge_target_abspath; + const char *wcroot_abspath; - return SVN_NO_ERROR; -} + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); -static svn_error_t * -assert_prop_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) -{ - apr_array_header_t *props_conflicted; + details = conflict->tree_conflict_local_details; - SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL, + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, conflict, scratch_pool, scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); - /* ### return proper error? */ - SVN_ERR_ASSERT(props_conflicted && props_conflicted->nelts > 0); + if (details->wc_siblings) + { + merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *); + } + else if (details->wc_move_targets && details->move_target_repos_relpath) + { + apr_array_header_t *moves; + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + merge_target_abspath = APR_ARRAY_IDX(moves, details->wc_move_target_idx, + const char *); + } + else + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Corresponding working copy node not found " + "for '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor( + wcroot_abspath, conflict->local_abspath), + scratch_pool)); - return SVN_NO_ERROR; -} + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, + merge_target_abspath, + scratch_pool, scratch_pool)); -static svn_error_t * -assert_tree_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) + /* Fetch the common ancestor file's content. */ + SVN_ERR(svn_stream_open_unique(&stream, &ancestor_tmp_abspath, wc_tmpdir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + url = svn_path_url_add_component2(repos_root_url, + incoming_old_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, + url, NULL, NULL, + FALSE, FALSE, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, stream, NULL, + &ancestor_props, scratch_pool)); + filter_props(ancestor_props, scratch_pool); + + /* Close stream to flush the file to disk. */ + SVN_ERR(svn_stream_close(stream)); + + /* Do the same for the incoming file's content. */ + SVN_ERR(svn_stream_open_unique(&stream, &incoming_tmp_abspath, wc_tmpdir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + url = svn_path_url_add_component2(repos_root_url, + incoming_new_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, + url, scratch_pool)); + SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, stream, NULL, + &incoming_props, scratch_pool)); + /* Close stream to flush the file to disk. */ + SVN_ERR(svn_stream_close(stream)); + + filter_props(incoming_props, scratch_pool); + + /* Create a property diff for the files. */ + SVN_ERR(svn_prop_diffs(&propdiffs, incoming_props, ancestor_props, + scratch_pool)); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(conflict->local_abspath, + merge_target_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Perform the file merge. */ + err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, + ctx->wc_ctx, + ancestor_tmp_abspath, incoming_tmp_abspath, + merge_target_abspath, + NULL, NULL, NULL, /* labels */ + NULL, NULL, /* conflict versions */ + FALSE, /* dry run */ + NULL, NULL, /* diff3_cmd, merge_options */ + apr_hash_count(ancestor_props) ? ancestor_props : NULL, + propdiffs, + NULL, NULL, /* conflict func/baton */ + NULL, NULL, /* don't allow user to cancel here */ + scratch_pool); + svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); + if (err) + return svn_error_compose_create(err, + svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath, + scratch_pool); + err = svn_error_compose_create(err, + svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + if (err) + return svn_error_trace(err); + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(merge_target_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (merge_content_outcome == svn_wc_merge_conflict) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->prop_state = merge_props_outcome; + notify->kind = svn_node_file; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + + /* And also about the successfully resolved tree conflict. */ + notify = svn_wc_create_notify(conflict->local_abspath, + svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + conflict->resolution_tree = svn_client_conflict_option_get_id(option); + + return SVN_NO_ERROR; +} + +/* Implements conflict_option_resolve_func_t. */ +static svn_error_t * +resolve_local_move_dir_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + struct conflict_tree_local_missing_details *details; + const char *merge_target_abspath; + const char *incoming_old_url; + const char *incoming_new_url; + svn_opt_revision_t incoming_old_opt_rev; + svn_opt_revision_t incoming_new_opt_rev; + svn_client__conflict_report_t *conflict_report; + + details = conflict->tree_conflict_local_details; + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + if (details->wc_move_targets) + { + apr_array_header_t *moves; + + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + merge_target_abspath = + APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *); + } + else + merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(conflict->local_abspath, + merge_target_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Resolve to current working copy state. + * svn_client__merge_locked() requires this. */ + err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath, + scratch_pool); + if (err) + goto unlock_wc; + + /* Merge outstanding changes to the merge target. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + incoming_new_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_new_repos_relpath, SVN_VA_NULL); + incoming_new_opt_rev.kind = svn_opt_revision_number; + incoming_new_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + incoming_new_url, &incoming_new_opt_rev, + merge_target_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); +unlock_wc: + svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); + err = svn_error_compose_create(err, + svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + if (err) + return svn_error_trace(err); + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(merge_target_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (conflict_report) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->kind = svn_node_dir; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + + /* And also about the successfully resolved tree conflict. */ + notify = svn_wc_create_notify(conflict->local_abspath, + svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + conflict->resolution_tree = svn_client_conflict_option_get_id(option); + + return SVN_NO_ERROR; +} + +static svn_error_t * +assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) +{ + svn_boolean_t text_conflicted; + + SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, NULL, NULL, + conflict, scratch_pool, + scratch_pool)); + + SVN_ERR_ASSERT(text_conflicted); /* ### return proper error? */ + + return SVN_NO_ERROR; +} + +static svn_error_t * +assert_prop_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) +{ + apr_array_header_t *props_conflicted; + + SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL, + conflict, scratch_pool, + scratch_pool)); + + /* ### return proper error? */ + SVN_ERR_ASSERT(props_conflicted && props_conflicted->nelts > 0); + + return SVN_NO_ERROR; +} + +static svn_error_t * +assert_tree_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) { svn_boolean_t tree_conflicted; @@ -10383,323 +10688,1052 @@ configure_option_incoming_delete_accept( } static svn_error_t * -describe_incoming_move_merge_conflict_option( - const char **description, +describe_incoming_move_merge_conflict_option( + const char **description, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + const char *moved_to_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_operation_t operation; + const char *victim_abspath; + svn_node_kind_t victim_node_kind; + const char *wcroot_abspath; + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + victim_abspath, scratch_pool, + scratch_pool)); + + operation = svn_client_conflict_get_operation(conflict); + if (operation == svn_wc_operation_merge) + { + const char *incoming_moved_abspath = NULL; + + if (victim_node_kind == svn_node_none) + { + /* This is an incoming move vs local move conflict. */ + struct conflict_tree_incoming_delete_details *details; + + details = conflict->tree_conflict_incoming_details; + if (details->wc_move_targets) + { + apr_array_header_t *moves; + + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + incoming_moved_abspath = + APR_ARRAY_IDX(moves, details->wc_move_target_idx, + const char *); + } + } + + if (incoming_moved_abspath) + { + /* The 'move and merge' option follows the incoming move; note that + * moved_to_abspath points to the current location of an item which + * was moved in the history of our merge target branch. If the user + * chooses 'move and merge', that item will be moved again (i.e. it + * will be moved to and merged with incoming_moved_abspath's item). */ + *description = + apr_psprintf( + result_pool, _("move '%s' to '%s' and merge"), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + moved_to_abspath), + scratch_pool), + svn_dirent_local_style(svn_dirent_skip_ancestor( + wcroot_abspath, + incoming_moved_abspath), + scratch_pool)); + } + else + { + *description = + apr_psprintf( + result_pool, _("move '%s' to '%s' and merge"), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + victim_abspath), + scratch_pool), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + moved_to_abspath), + scratch_pool)); + } + } + else + *description = + apr_psprintf( + result_pool, _("move and merge local changes from '%s' into '%s'"), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + victim_abspath), + scratch_pool), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + moved_to_abspath), + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Configure 'incoming move file merge' resolution option for + * a tree conflict. */ +static svn_error_t * +configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t victim_node_kind; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + if (victim_node_kind == svn_node_file && + incoming_old_kind == svn_node_file && + incoming_new_kind == svn_node_none && + incoming_change == svn_wc_conflict_action_delete && + local_change == svn_wc_conflict_reason_edited) + { + struct conflict_tree_incoming_delete_details *details; + const char *description; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->moves == NULL) + return SVN_NO_ERROR; + + if (apr_hash_count(details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); + SVN_ERR(describe_incoming_move_merge_conflict_option(&description, + conflict, ctx, + moved_to_abspath, + scratch_pool, + scratch_pool)); + add_resolution_option( + options, conflict, + svn_client_conflict_option_incoming_move_file_text_merge, + _("Move and merge"), description, + resolve_incoming_move_file_text_merge); + } + + return SVN_NO_ERROR; +} + +/* Configure 'incoming move dir merge' resolution option for + * a tree conflict. */ +static svn_error_t * +configure_option_incoming_dir_merge(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t victim_node_kind; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + if (victim_node_kind == svn_node_dir && + incoming_old_kind == svn_node_dir && + incoming_new_kind == svn_node_none && + incoming_change == svn_wc_conflict_action_delete && + local_change == svn_wc_conflict_reason_edited) + { + struct conflict_tree_incoming_delete_details *details; + const char *description; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->moves == NULL) + return SVN_NO_ERROR; + + if (apr_hash_count(details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); + SVN_ERR(describe_incoming_move_merge_conflict_option(&description, + conflict, ctx, + moved_to_abspath, + scratch_pool, + scratch_pool)); + add_resolution_option(options, conflict, + svn_client_conflict_option_incoming_move_dir_merge, + _("Move and merge"), description, + resolve_incoming_move_dir_merge); + } + + return SVN_NO_ERROR; +} + +/* Configure 'local move file merge' resolution option for + * a tree conflict. */ +static svn_error_t * +configure_option_local_move_file_or_dir_merge( + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, + scratch_pool, scratch_pool)); + + details = conflict->tree_conflict_local_details; + if (details != NULL && details->moves != NULL && + details->move_target_repos_relpath != NULL) + { + apr_array_header_t *moves; + const char *moved_to_abspath; + const char *description; + + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + moved_to_abspath = + APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *); + + description = + apr_psprintf( + scratch_pool, _("apply changes to move destination '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, moved_to_abspath), + scratch_pool)); + + if ((incoming_old_kind == svn_node_file || + incoming_old_kind == svn_node_none) && + (incoming_new_kind == svn_node_file || + incoming_new_kind == svn_node_none)) + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_local_move_file_text_merge, + _("Apply to move destination"), + description, resolve_local_move_file_merge); + } + else + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_local_move_dir_merge, + _("Apply to move destination"), + description, resolve_local_move_dir_merge); + } + } + } + + return SVN_NO_ERROR; +} + +/* Configure 'sibling move file/dir merge' resolution option for + * a tree conflict. */ +static svn_error_t * +configure_option_sibling_move_merge(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, + scratch_pool, scratch_pool)); + + details = conflict->tree_conflict_local_details; + if (details != NULL && details->wc_siblings != NULL) + { + const char *description; + const char *sibling; + + sibling = + apr_pstrdup(conflict->pool, + APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *)); + description = + apr_psprintf( + scratch_pool, _("apply changes to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, sibling), + scratch_pool)); + + if ((incoming_old_kind == svn_node_file || + incoming_old_kind == svn_node_none) && + (incoming_new_kind == svn_node_file || + incoming_new_kind == svn_node_none)) + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_sibling_move_file_text_merge, + _("Apply to corresponding local location"), + description, resolve_local_move_file_merge); + } + else + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_sibling_move_dir_merge, + _("Apply to corresponding local location"), + description, resolve_local_move_dir_merge); + } + } + } + + return SVN_NO_ERROR; +} + +struct conflict_tree_update_local_moved_away_details { + /* + * This array consists of "const char *" absolute paths to working copy + * nodes which are uncomitted copies and correspond to the repository path + * of the conflict victim. + * Each such working copy node is a potential local move target which can + * be chosen to find a suitable merge target when resolving a tree conflict. + * + * This may be an empty array in case if there is no move target path in + * the working copy. */ + apr_array_header_t *wc_move_targets; + + /* Current index into the list of working copy paths in WC_MOVE_TARGETS. */ + int preferred_move_target_idx; +}; + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the + * incoming move's target location to the local move's target location, + * overriding the incoming move. The original local move was broken during + * update/switch, so overriding the incoming move involves recording a new + * move from the incoming move's target location to the local move's target + * location. */ +static svn_error_t * +resolve_both_moved_file_update_keep_local_move( + svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *wc_tmpdir; + const char *ancestor_abspath; + svn_stream_t *ancestor_stream; + apr_hash_t *ancestor_props; + apr_hash_t *incoming_props; + apr_hash_t *local_props; + const char *ancestor_url; + const char *corrected_url; + svn_ra_session_t *ra_session; + svn_wc_merge_outcome_t merge_content_outcome; + svn_wc_notify_state_t merge_props_outcome; + apr_array_header_t *propdiffs; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_update_local_moved_away_details *local_details; + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + /* Set up temporary storage for the common ancestor version of the file. */ + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(&ancestor_stream, + &ancestor_abspath, wc_tmpdir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + /* Fetch the ancestor file's content. */ + ancestor_url = svn_path_url_add_component2(repos_root_url, + incoming_old_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, + ancestor_url, NULL, NULL, + FALSE, FALSE, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, + ancestor_stream, NULL, /* fetched_rev */ + &ancestor_props, scratch_pool)); + filter_props(ancestor_props, scratch_pool); + + /* Close stream to flush ancestor file to disk. */ + SVN_ERR(svn_stream_close(ancestor_stream)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moved_to_abspath = + APR_ARRAY_IDX(local_details->wc_move_targets, + local_details->preferred_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Get a copy of the incoming moved item's properties. */ + err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, + incoming_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Get a copy of the local move target's properties. */ + err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, + local_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Create a property diff for the files. */ + err = svn_prop_diffs(&propdiffs, incoming_props, local_props, + scratch_pool); + if (err) + goto unlock_wc; + + /* Perform the file merge. */ + err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, + ctx->wc_ctx, ancestor_abspath, + incoming_moved_to_abspath, local_moved_to_abspath, + NULL, NULL, NULL, /* labels */ + NULL, NULL, /* conflict versions */ + FALSE, /* dry run */ + NULL, NULL, /* diff3_cmd, merge_options */ + apr_hash_count(ancestor_props) ? ancestor_props : NULL, + propdiffs, + NULL, NULL, /* conflict func/baton */ + NULL, NULL, /* don't allow user to cancel here */ + scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(local_moved_to_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (merge_content_outcome == svn_wc_merge_conflict) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->prop_state = merge_props_outcome; + notify->kind = svn_node_file; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + /* Record a new move which overrides the incoming move. */ + err = svn_wc__move2(ctx->wc_ctx, incoming_moved_to_abspath, + local_moved_to_abspath, + TRUE, /* meta-data only move */ + FALSE, /* mixed-revisions don't apply to files */ + NULL, NULL, /* don't allow user to cancel here */ + NULL, NULL, /* no extra notification */ + scratch_pool); + if (err) + goto unlock_wc; + + /* Remove moved-away file from disk. */ + err = svn_io_remove_file2(incoming_moved_to_abspath, TRUE, scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the
[... 1005 lines stripped ...]
