From commits-return-49360-archive-asf-public=cust-asf.ponee.io@subversion.apache.org Sun Sep 23 11:43:46 2018 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx-eu-01.ponee.io (Postfix) with SMTP id E5815180658 for ; Sun, 23 Sep 2018 11:43:43 +0200 (CEST) Received: (qmail 34708 invoked by uid 500); 23 Sep 2018 09:43:43 -0000 Mailing-List: contact commits-help@subversion.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@subversion.apache.org Delivered-To: mailing list commits@subversion.apache.org Received: (qmail 34698 invoked by uid 99); 23 Sep 2018 09:43:42 -0000 Received: from Unknown (HELO svn01-us-west.apache.org) (209.188.14.144) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 23 Sep 2018 09:43:42 +0000 Received: from svn01-us-west.apache.org (localhost [127.0.0.1]) by svn01-us-west.apache.org (ASF Mail Server at svn01-us-west.apache.org) with ESMTP id 1FE4D3A00A1 for ; Sun, 23 Sep 2018 09:43:42 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1841732 - in /subversion/branches/1.11.x: ./ STATUS subversion/include/svn_client.h subversion/libsvn_client/conflicts.c subversion/svn/conflict-callbacks.c subversion/tests/libsvn_client/conflicts-test.c Date: Sun, 23 Sep 2018 09:43:41 -0000 To: commits@subversion.apache.org From: stsp@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20180923094342.1FE4D3A00A1@svn01-us-west.apache.org> Author: stsp Date: Sun Sep 23 09:43:41 2018 New Revision: 1841732 URL: http://svn.apache.org/viewvc?rev=1841732&view=rev Log: Merge r1840990, r1840991, r1840995, and r1840997 from trunk. * r1840990, r1840991, r1840995, r1840997 Various conflict resolver improvements. Notes: I am proposing these in a batch because merging them out of order would result in conflicts, and because I was planning to make these changes before 1.11 was branched in the first place but didn't get around to it. - r1840990 is a cosmetic output change - r1840991 prevents an out-of-bounds array access - r1840995 implements support for ambiguous moves in 'local missing' cases - r1840997 improves resolver API semantics Ideally, these should be merged before 1.11.0 is released. Votes: +0: julianfoad (only r1840990,r1840991) +1: stsp, brane, jcorvel Modified: subversion/branches/1.11.x/ (props changed) subversion/branches/1.11.x/STATUS subversion/branches/1.11.x/subversion/include/svn_client.h subversion/branches/1.11.x/subversion/libsvn_client/conflicts.c subversion/branches/1.11.x/subversion/svn/conflict-callbacks.c subversion/branches/1.11.x/subversion/tests/libsvn_client/conflicts-test.c Propchange: subversion/branches/1.11.x/ ------------------------------------------------------------------------------ --- svn:mergeinfo (original) +++ svn:mergeinfo Sun Sep 23 09:43:41 2018 @@ -99,4 +99,4 @@ /subversion/branches/verify-at-commit:1462039-1462408 /subversion/branches/verify-keep-going:1439280-1546110 /subversion/branches/wc-collate-path:1402685-1480384 -/subversion/trunk:1841091,1841098,1841136,1841272 +/subversion/trunk:1840990-1840991,1840995,1840997,1841091,1841098,1841136,1841272 Modified: subversion/branches/1.11.x/STATUS URL: http://svn.apache.org/viewvc/subversion/branches/1.11.x/STATUS?rev=1841732&r1=1841731&r2=1841732&view=diff ============================================================================== --- subversion/branches/1.11.x/STATUS (original) +++ subversion/branches/1.11.x/STATUS Sun Sep 23 09:43:41 2018 @@ -61,19 +61,3 @@ Approved changes: +1: danielsh (confirmed the buildsystem parts won't affect anything other than the java bindings; this counts as three +1s for the build system changes) - - * r1840990, r1840991, r1840995, r1840997 - Various conflict resolver improvements. - Notes: - I am proposing these in a batch because merging them out of order would - result in conflicts, and because I was planning to make these changes - before 1.11 was branched in the first place but didn't get around to it. - - r1840990 is a cosmetic output change - - r1840991 prevents an out-of-bounds array access - - r1840995 implements support for ambiguous moves in 'local missing' cases - - r1840997 improves resolver API semantics - Ideally, these should be merged before 1.11.0 is released. - Votes: - +0: julianfoad (only r1840990,r1840991) - +1: stsp, brane, jcorvel - Modified: subversion/branches/1.11.x/subversion/include/svn_client.h URL: http://svn.apache.org/viewvc/subversion/branches/1.11.x/subversion/include/svn_client.h?rev=1841732&r1=1841731&r2=1841732&view=diff ============================================================================== --- subversion/branches/1.11.x/subversion/include/svn_client.h (original) +++ subversion/branches/1.11.x/subversion/include/svn_client.h Sun Sep 23 09:43:41 2018 @@ -4585,27 +4585,45 @@ svn_client_conflict_option_set_merged_pr const svn_string_t *merged_propval); /** - * Get a list of possible repository paths which can be applied to the - * svn_client_conflict_option_incoming_move_file_text_merge, or - * svn_client_conflict_option_local_move_file_text_merge, or - * svn_client_conflict_option_incoming_move_dir_merge resolution - * @a option. (If a different option is passed in, this function will - * raise an assertion failure.) - * - * In some situations, there can be multiple possible destinations for an - * incoming move. One such situation is where a file was copied and moved - * in the same revision: svn cp a b; svn mv a c; svn commit + * Get a list of possible repository paths which can be applied to @a option. + * + * In some situations, there can be multiple possible destinations for a move. + * One such situation is where a file was copied and moved in the same revision: + * svn cp a b; svn mv a c; svn commit * When this move is merged elsewhere, both b and c will appear as valid move * destinations to the conflict resolver. To resolve such ambiguity, the client * may call this function to obtain a list of possible destinations the user * may choose from. * + * @a *possible_moved_to_repos_relpaths is set to NULL if the @a option does + * not support multiple move targets. API users may assume that only one option + * among those which can be applied to a conflict supports move targets. + * * The array is allocated in @a result_pool and will have "const char *" * elements pointing to strings also allocated in @a result_pool. * All paths are relpaths, and relative to the repository root. * - * @see svn_client_conflict_option_set_moved_to_repos_relpath() + * @see svn_client_conflict_option_set_moved_to_repos_relpath2() + * @since New in 1.11. + */ +svn_error_t * +svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( + apr_array_header_t **possible_moved_to_repos_relpaths, + svn_client_conflict_option_t *option, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/** + * Get a list of possible repository paths which can be applied to the + * svn_client_conflict_option_incoming_move_file_text_merge, or the + * svn_client_conflict_option_incoming_move_dir_merge resolution @a option. + * + * In SVN 1.10, if a different option is passed in, this function will + * raise an assertion failure. Otherwise this function behaves just like + * svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(). + * * @since New in 1.10. + * @deprecated use svn_client_conflict_option_get_moved_to_repos_relpath_candidates2() */ svn_error_t * svn_client_conflict_option_get_moved_to_repos_relpath_candidates( @@ -4615,10 +4633,9 @@ svn_client_conflict_option_get_moved_to_ apr_pool_t *scratch_pool); /** - * Set the preferred moved target repository path for the - * svn_client_conflict_option_incoming_move_file_text_merge or - * svn_client_conflict_option_incoming_move_dir_merge resolution option. - * + * Set the preferred moved target repository path. If @a option is not + * applicable to a moved target repository path, do nothing. + * * @a preferred_move_target_idx must be a valid index into the list returned * by svn_client_conflict_option_get_moved_to_repos_relpath_candidates(). * @@ -4627,7 +4644,23 @@ svn_client_conflict_option_get_moved_to_ * svn_client_conflict_option_get_description(). Call these functions again * to get updated descriptions containing the newly selected move target. * + * @since New in 1.11. + */ +svn_error_t * +svn_client_conflict_option_set_moved_to_repos_relpath2( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool); + +/** + * Like svn_client_conflict_option_set_moved_to_repos_relpath2(), except + * that in SVN 1.10 it raises an assertion failure if an option other + * than svn_client_conflict_option_incoming_move_file_text_merge or + * svn_client_conflict_option_incoming_move_dir_merge is passed. + * * @since New in 1.10. + * @deprecated use svn_client_conflict_option_set_moved_to_repos_relpath2() */ svn_error_t * svn_client_conflict_option_set_moved_to_repos_relpath( @@ -4638,25 +4671,45 @@ svn_client_conflict_option_set_moved_to_ /** * Get a list of possible moved-to abspaths in the working copy which can be - * applied to the svn_client_conflict_option_incoming_move_file_text_merge, - * svn_client_conflict_option_local_move_file_text_merge, - * or svn_client_conflict_option_incoming_move_dir_merge resolution @a option. - * (If a different option is passed in, this function will raise an assertion - * failure.) - * - * All paths in the returned list correspond to the repository path which - * is assumed to be the destination of the incoming move operation. - * To support cases where this destination path is ambiguous, the client may - * call svn_client_conflict_option_get_moved_to_repos_relpath_candidates() - * before calling this function to let the user select a repository path first. + * applied to @a option. + * + * All working copy paths in the returned list correspond to one repository + * path which is be one of the possible destinations of a move operation. + * More than one repository-side move target candidate may exist; call + * svn_client_conflict_option_get_moved_to_repos_relpath_candidates() before + * calling this function to let the user select a repository path first. + * Otherwise, one of the repository-side paths will be selected internally. * + * @a *possible_moved_to_abspaths is set to NULL if the @a option does not + * support multiple move targets. API users may assume that only one option + * among those which can be applied to a conflict supports move targets. + * * If no possible moved-to paths can be found, return an empty array. * This doesn't mean that no move happened in the repository. It is possible * that the move destination is outside the scope of the current working copy, * for example, in which case the conflict must be resolved in some other way. * - * @see svn_client_conflict_option_set_moved_to_abspath() + * @see svn_client_conflict_option_set_moved_to_abspath2() + * @since New in 1.11. + */ +svn_error_t * +svn_client_conflict_option_get_moved_to_abspath_candidates2( + apr_array_header_t **possible_moved_to_abspaths, + svn_client_conflict_option_t *option, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/** + * Get a list of possible moved-to abspaths in the working copy which can be + * svn_client_conflict_option_incoming_move_file_text_merge, or the + * svn_client_conflict_option_incoming_move_dir_merge resolution @a option. + * + * In SVN 1.10, if a different option is passed in, this function will + * raise an assertion failure. Otherwise this function behaves just like + * svn_client_conflict_option_get_moved_to_abspath_candidates2(). + * * @since New in 1.10. + * @deprecated use svn_client_conflict_option_get_moved_to_abspath_candidates2() */ svn_error_t * svn_client_conflict_option_get_moved_to_abspath_candidates( @@ -4666,15 +4719,34 @@ svn_client_conflict_option_get_moved_to_ apr_pool_t *scratch_pool); /** - * Set the preferred moved target abspath for the - * svn_client_conflict_option_incoming_move_file_text_merge or - * svn_client_conflict_option_local_move_file_text_merge or - * svn_client_conflict_option_incoming_move_dir_merge resolution option. + * Set the preferred moved target working copy path. If @a option is not + * applicable to a moved target working copy path, do nothing. * * @a preferred_move_target_idx must be a valid index into the list - * returned by svn_client_conflict_option_get_moved_to_abspath_candidates(). + * returned by svn_client_conflict_option_get_moved_to_abspath_candidates2(). * + * This function can be called multiple times. + * It affects the output of svn_client_conflict_tree_get_description() and + * svn_client_conflict_option_get_description(). Call these functions again + * to get updated descriptions containing the newly selected move target. + * + * @since New in 1.11. + */ +svn_error_t * +svn_client_conflict_option_set_moved_to_abspath2( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool); + +/** + * Like svn_client_conflict_option_set_moved_to_abspath2(), except that + * in SVN 1.10 this function raises an assertion failure if an option + * other than svn_client_conflict_option_incoming_move_file_text_merge or + * svn_client_conflict_option_incoming_move_dir_merge is passed. + * * @since New in 1.10. + * @deprecated use svn_client_conflict_option_set_moved_to_abspath2() */ svn_error_t * svn_client_conflict_option_set_moved_to_abspath( Modified: subversion/branches/1.11.x/subversion/libsvn_client/conflicts.c URL: http://svn.apache.org/viewvc/subversion/branches/1.11.x/subversion/libsvn_client/conflicts.c?rev=1841732&r1=1841731&r2=1841732&view=diff ============================================================================== --- subversion/branches/1.11.x/subversion/libsvn_client/conflicts.c (original) +++ subversion/branches/1.11.x/subversion/libsvn_client/conflicts.c Sun Sep 23 09:43:41 2018 @@ -2350,6 +2350,8 @@ struct conflict_tree_local_missing_detai /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */ svn_revnum_t deleted_rev; + /* ### Add 'added_rev', like in conflict_tree_incoming_delete_details? */ + /* Author who committed DELETED_REV. */ const char *deleted_rev_author; @@ -2357,17 +2359,38 @@ struct conflict_tree_local_missing_detai const char *deleted_repos_relpath; /* Move information about the conflict victim. If not NULL, this is an - * array of repos_move_info elements. Each element is the head of a - * move chain which starts in DELETED_REV. */ + * array of 'struct repos_move_info *' elements. Each element is the + * head of a move chain which starts in DELETED_REV. */ apr_array_header_t *moves; - /* If not NULL, this is the move target abspath. */ - const char *moved_to_abspath; + /* If moves is not NULL, a map of repos_relpaths and working copy nodes. + * + * Each key is a "const char *" repository relpath corresponding to a + * possible repository-side move destination node in the revision which + * is the merge-right revision in case of a merge. + * + * Each value is an apr_array_header_t *. + * Each array consists of "const char *" absolute paths to working copy + * nodes which correspond to the repository node selected by the map key. + * 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 hash map in case if there is no move target path + * in the working copy. */ + apr_hash_t *wc_move_targets; + + /* If not NULL, the preferred move target repository relpath. This is our key + * into the WC_MOVE_TARGETS map above (can be overridden by the user). */ + const char *move_target_repos_relpath; + + /* The current index into the list of working copy nodes corresponding to + * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */ + int wc_move_target_idx; /* Move information about siblings. Siblings are nodes which share * a youngest common ancestor with the conflict victim. E.g. in case * of a merge operation they are part of the merge source branch. - * If not NULL, this is an array of repos_move_info elements. + * If not NULL, this is an array of 'struct repos_move_info *' elements. * Each element is the head of a move chain, which starts at some * point in history after siblings and conflict victim forked off * their common ancestor. */ @@ -2635,6 +2658,133 @@ collect_sibling_move_candidates(apr_arra return SVN_NO_ERROR; } +/* Follow each move chain starting a MOVE all the way to the end to find + * the possible working copy locations for VICTIM_ABSPATH which corresponds + * to VICTIM_REPOS_REPLATH@VICTIM_REVISION. + * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the + * repos_relpath which is the corresponding move destination in the repository. + * This function is recursive. */ +static svn_error_t * +follow_move_chains(apr_hash_t *wc_move_targets, + struct repos_move_info *move, + svn_client_ctx_t *ctx, + const char *victim_abspath, + svn_node_kind_t victim_node_kind, + const char *victim_repos_relpath, + svn_revnum_t victim_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* If this is the end of a move chain, look for matching paths in + * the working copy and add them to our collection if found. */ + if (move->next == NULL) + { + apr_array_header_t *candidate_abspaths; + + /* Gather candidate nodes which represent this moved_to_repos_relpath. */ + SVN_ERR(svn_wc__guess_incoming_move_target_nodes( + &candidate_abspaths, ctx->wc_ctx, + victim_abspath, victim_node_kind, + move->moved_to_repos_relpath, + scratch_pool, scratch_pool)); + if (candidate_abspaths->nelts > 0) + { + apr_array_header_t *moved_to_abspaths; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + moved_to_abspaths = apr_array_make(result_pool, 1, + sizeof (const char *)); + + for (i = 0; i < candidate_abspaths->nelts; i++) + { + const char *candidate_abspath; + const char *repos_root_url; + const char *repos_uuid; + const char *candidate_repos_relpath; + svn_revnum_t candidate_revision; + + svn_pool_clear(iterpool); + + candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i, + const char *); + SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, + &candidate_repos_relpath, + &repos_root_url, + &repos_uuid, + NULL, NULL, + ctx->wc_ctx, + candidate_abspath, + FALSE, + iterpool, iterpool)); + + if (candidate_revision == SVN_INVALID_REVNUM) + continue; + + /* If the conflict victim and the move target candidate + * are not from the same revision we must ensure that + * they are related. */ + if (candidate_revision != victim_revision) + { + svn_client__pathrev_t *yca_loc; + svn_error_t *err; + + err = find_yca(&yca_loc, victim_repos_relpath, + victim_revision, + candidate_repos_relpath, + candidate_revision, + repos_root_url, repos_uuid, + NULL, ctx, iterpool, iterpool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + + if (yca_loc == NULL) + continue; + } + + APR_ARRAY_PUSH(moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, candidate_abspath); + } + svn_pool_destroy(iterpool); + + svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath, + moved_to_abspaths); + } + } + else + { + int i; + apr_pool_t *iterpool; + + /* Recurse into each of the possible move chains. */ + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < move->next->nelts; i++) + { + struct repos_move_info *next_move; + + svn_pool_clear(iterpool); + + next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); + SVN_ERR(follow_move_chains(wc_move_targets, next_move, + ctx, victim_abspath, victim_node_kind, + victim_repos_relpath, victim_revision, + result_pool, iterpool)); + + } + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + /* Implements tree_conflict_get_details_func_t. */ static svn_error_t * conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, @@ -2862,6 +3012,51 @@ conflict_tree_get_details_local_missing( deleted_basename, conflict->pool); details->moves = moves; + if (details->moves != NULL) + { + apr_pool_t *iterpool; + int i; + + details->wc_move_targets = apr_hash_make(conflict->pool); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < details->moves->nelts; i++) + { + struct repos_move_info *move; + + svn_pool_clear(iterpool); + move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); + SVN_ERR(follow_move_chains(details->wc_move_targets, move, ctx, + conflict->local_abspath, + svn_node_file, + new_repos_relpath, + new_rev, + scratch_pool, iterpool)); + } + svn_pool_destroy(iterpool); + + if (apr_hash_count(details->wc_move_targets) > 0) + { + apr_array_header_t *move_target_repos_relpaths; + const svn_sort__item_t *item; + + /* Initialize to the first possible move target. Hopefully, + * in most cases there will only be one candidate anyway. */ + move_target_repos_relpaths = svn_sort__hash( + details->wc_move_targets, + svn_sort_compare_items_as_paths, + scratch_pool); + item = &APR_ARRAY_IDX(move_target_repos_relpaths, + 0, svn_sort__item_t); + details->move_target_repos_relpath = item->key; + details->wc_move_target_idx = 0; + } + else + { + details->move_target_repos_relpath = NULL; + details->wc_move_target_idx = 0; + } + } + details->sibling_moves = sibling_moves; details->wc_siblings = wc_siblings; if (details->wc_siblings && details->wc_siblings->nelts == 1) @@ -2875,7 +3070,7 @@ conflict_tree_get_details_local_missing( conflict->recommended_option_id = svn_client_conflict_option_sibling_move_dir_merge; } - + conflict->tree_conflict_local_details = details; return SVN_NO_ERROR; @@ -4784,176 +4979,49 @@ get_incoming_delete_details_for_reverse_ return SVN_NO_ERROR; } -/* Follow each move chain starting a MOVE all the way to the end to find - * the possible working copy locations for VICTIM_ABSPATH which corresponds - * to VICTIM_REPOS_REPLATH@VICTIM_REVISION. - * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the - * repos_relpath which is the corresponding move destination in the repository. - * This function is recursive. */ static svn_error_t * -follow_move_chains(apr_hash_t *wc_move_targets, - struct repos_move_info *move, - svn_client_ctx_t *ctx, - const char *victim_abspath, - svn_node_kind_t victim_node_kind, - const char *victim_repos_relpath, - svn_revnum_t victim_revision, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { - /* If this is the end of a move chain, look for matching paths in - * the working copy and add them to our collection if found. */ - if (move->next == NULL) - { - apr_array_header_t *candidate_abspaths; + int i; + const char *victim_abspath; + svn_node_kind_t victim_node_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; - /* Gather candidate nodes which represent this moved_to_repos_relpath. */ - SVN_ERR(svn_wc__guess_incoming_move_target_nodes( - &candidate_abspaths, ctx->wc_ctx, - victim_abspath, victim_node_kind, - move->moved_to_repos_relpath, - scratch_pool, scratch_pool)); - if (candidate_abspaths->nelts > 0) - { - apr_array_header_t *moved_to_abspaths; - int i; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); + /* ### Should we get the old location in case of reverse-merges? */ + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, + scratch_pool, scratch_pool)); + details->wc_move_targets = apr_hash_make(conflict->pool); + for (i = 0; i < details->moves->nelts; i++) + { + struct repos_move_info *move; - moved_to_abspaths = apr_array_make(result_pool, 1, - sizeof (const char *)); + move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); + SVN_ERR(follow_move_chains(details->wc_move_targets, move, + ctx, victim_abspath, + victim_node_kind, + incoming_new_repos_relpath, + incoming_new_pegrev, + conflict->pool, scratch_pool)); + } - for (i = 0; i < candidate_abspaths->nelts; i++) - { - const char *candidate_abspath; - const char *repos_root_url; - const char *repos_uuid; - const char *candidate_repos_relpath; - svn_revnum_t candidate_revision; + /* Initialize to the first possible move target. Hopefully, + * in most cases there will only be one candidate anyway. */ + details->move_target_repos_relpath = + get_moved_to_repos_relpath(details, scratch_pool); + details->wc_move_target_idx = 0; - svn_pool_clear(iterpool); - - candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i, - const char *); - SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, - &candidate_repos_relpath, - &repos_root_url, - &repos_uuid, - NULL, NULL, - ctx->wc_ctx, - candidate_abspath, - FALSE, - iterpool, iterpool)); - - if (candidate_revision == SVN_INVALID_REVNUM) - continue; - - /* If the conflict victim and the move target candidate - * are not from the same revision we must ensure that - * they are related. */ - if (candidate_revision != victim_revision) - { - svn_client__pathrev_t *yca_loc; - svn_error_t *err; - - err = find_yca(&yca_loc, victim_repos_relpath, - victim_revision, - candidate_repos_relpath, - candidate_revision, - repos_root_url, repos_uuid, - NULL, ctx, iterpool, iterpool); - if (err) - { - if (err->apr_err == SVN_ERR_FS_NOT_FOUND) - { - svn_error_clear(err); - yca_loc = NULL; - } - else - return svn_error_trace(err); - } - - if (yca_loc == NULL) - continue; - } - - APR_ARRAY_PUSH(moved_to_abspaths, const char *) = - apr_pstrdup(result_pool, candidate_abspath); - } - svn_pool_destroy(iterpool); - - svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath, - moved_to_abspaths); - } - } - else - { - int i; - apr_pool_t *iterpool; - - /* Recurse into each of the possible move chains. */ - iterpool = svn_pool_create(scratch_pool); - for (i = 0; i < move->next->nelts; i++) - { - struct repos_move_info *next_move; - - svn_pool_clear(iterpool); - - next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); - SVN_ERR(follow_move_chains(wc_move_targets, next_move, - ctx, victim_abspath, victim_node_kind, - victim_repos_relpath, victim_revision, - result_pool, iterpool)); - - } - svn_pool_destroy(iterpool); - } - - return SVN_NO_ERROR; -} - -static svn_error_t * -init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, - svn_client_conflict_t *conflict, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - int i; - const char *victim_abspath; - svn_node_kind_t victim_node_kind; - const char *incoming_new_repos_relpath; - svn_revnum_t incoming_new_pegrev; - - victim_abspath = svn_client_conflict_get_local_abspath(conflict); - victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); - /* ### Should we get the old location in case of reverse-merges? */ - SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( - &incoming_new_repos_relpath, &incoming_new_pegrev, - NULL, conflict, - scratch_pool, scratch_pool)); - details->wc_move_targets = apr_hash_make(conflict->pool); - for (i = 0; i < details->moves->nelts; i++) - { - struct repos_move_info *move; - - move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); - SVN_ERR(follow_move_chains(details->wc_move_targets, move, - ctx, victim_abspath, - victim_node_kind, - incoming_new_repos_relpath, - incoming_new_pegrev, - conflict->pool, scratch_pool)); - } - - /* Initialize to the first possible move target. Hopefully, - * in most cases there will only be one candidate anyway. */ - details->move_target_repos_relpath = - get_moved_to_repos_relpath(details, scratch_pool); - details->wc_move_target_idx = 0; - - /* If only one move target exists recommend a resolution option. */ - if (apr_hash_count(details->wc_move_targets) == 1) - { - apr_array_header_t *wc_abspaths; + /* If only one move target exists recommend a resolution option. */ + if (apr_hash_count(details->wc_move_targets) == 1) + { + apr_array_header_t *wc_abspaths; wc_abspaths = svn_hash_gets(details->wc_move_targets, details->move_target_repos_relpath); @@ -9004,6 +9072,11 @@ resolve_local_move_file_merge(svn_client 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)); details = conflict->tree_conflict_local_details; @@ -9019,12 +9092,28 @@ resolve_local_move_file_merge(svn_client NULL, conflict, scratch_pool, scratch_pool)); - if (details->wc_siblings) /* local missing */ - merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, - details->preferred_sibling_idx, - const char *); - else /* local move */ - merge_target_abspath = details->moved_to_abspath; + 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, @@ -9381,13 +9470,13 @@ svn_client_conflict_text_get_resolution_ add_resolution_option(*options, conflict, svn_client_conflict_option_incoming_text_where_conflicted, _("Accept incoming for conflicts"), - _("accept changes only where they conflict"), + _("accept incoming changes only where they conflict"), resolve_text_conflict); add_resolution_option(*options, conflict, svn_client_conflict_option_working_text_where_conflicted, _("Reject conflicts"), - _("reject changes which conflict and accept the rest"), + _("reject incoming changes which conflict and accept the rest"), resolve_text_conflict); add_resolution_option(*options, conflict, @@ -10291,64 +10380,30 @@ configure_option_local_move_file_merge(s scratch_pool, scratch_pool)); details = conflict->tree_conflict_local_details; - if (details != NULL && details->moves != NULL) + if (details != NULL && details->moves != NULL && + details->move_target_repos_relpath != NULL) { - apr_hash_t *wc_move_targets = apr_hash_make(scratch_pool); - apr_pool_t *iterpool; - int i; - - iterpool = svn_pool_create(scratch_pool); - for (i = 0; i < details->moves->nelts; i++) - { - struct repos_move_info *move; - - svn_pool_clear(iterpool); - move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); - SVN_ERR(follow_move_chains(wc_move_targets, move, ctx, - conflict->local_abspath, - svn_node_file, - incoming_new_repos_relpath, - incoming_new_pegrev, - scratch_pool, iterpool)); - } - svn_pool_destroy(iterpool); - - if (apr_hash_count(wc_move_targets) > 0) - { - apr_array_header_t *move_target_repos_relpaths; - const svn_sort__item_t *item; - apr_array_header_t *moved_to_abspaths; - const char *description; + apr_array_header_t *moves; + const char *moved_to_abspath; + const char *description; - /* Initialize to the first possible move target. Hopefully, - * in most cases there will only be one candidate anyway. */ - move_target_repos_relpaths = svn_sort__hash( - wc_move_targets, - svn_sort_compare_items_as_paths, - scratch_pool); - item = &APR_ARRAY_IDX(move_target_repos_relpaths, - 0, svn_sort__item_t); - moved_to_abspaths = item->value; - details->moved_to_abspath = - apr_pstrdup(conflict->pool, - APR_ARRAY_IDX(moved_to_abspaths, 0, const char *)); + 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, - details->moved_to_abspath), - scratch_pool)); + 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)); - 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 - details->moved_to_abspath = NULL; + add_resolution_option( + options, conflict, + svn_client_conflict_option_local_move_file_text_merge, + _("Apply to move destination"), + description, resolve_local_move_file_merge); } } @@ -10439,43 +10494,24 @@ configure_option_sibling_move_merge(svn_ return SVN_NO_ERROR; } -svn_error_t * -svn_client_conflict_option_get_moved_to_repos_relpath_candidates( +/* Return a copy of the repos replath candidate list. */ +static svn_error_t * +get_repos_relpath_candidates( apr_array_header_t **possible_moved_to_repos_relpaths, - svn_client_conflict_option_t *option, + apr_hash_t *wc_move_targets, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; - const char *victim_abspath; apr_array_header_t *sorted_repos_relpaths; int i; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); - - victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Getting a list of possible move targets " - "requires details for tree conflict at '%s' " - "to be fetched from the repository first"), - svn_dirent_local_style(victim_abspath, - scratch_pool)); - - /* Return a copy of the repos replath candidate list. */ - sorted_repos_relpaths = svn_sort__hash(details->wc_move_targets, + sorted_repos_relpaths = svn_sort__hash(wc_move_targets, svn_sort_compare_items_as_paths, scratch_pool); - *possible_moved_to_repos_relpaths = apr_array_make( - result_pool, - sorted_repos_relpaths->nelts, - sizeof (const char *)); + *possible_moved_to_repos_relpaths = + apr_array_make(result_pool, sorted_repos_relpaths->nelts, + sizeof (const char *)); for (i = 0; i < sorted_repos_relpaths->nelts; i++) { svn_sort__item_t item; @@ -10491,37 +10527,107 @@ svn_client_conflict_option_get_moved_to_ } svn_error_t * -svn_client_conflict_option_set_moved_to_repos_relpath( +svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( + apr_array_header_t **possible_moved_to_repos_relpaths, svn_client_conflict_option_t *option, - int preferred_move_target_idx, - svn_client_ctx_t *ctx, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; const char *victim_abspath; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + svn_client_conflict_option_id_t id; + + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge) + { + /* We cannot operate on this option. */ + *possible_moved_to_repos_relpaths = NULL; + return SVN_NO_ERROR; + } + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + 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); + + 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; + + details = conflict->tree_conflict_local_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths, + details->wc_move_targets, + result_pool, scratch_pool)); + } + else + { + struct conflict_tree_incoming_delete_details *details; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths, + details->wc_move_targets, + result_pool, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + apr_array_header_t **possible_moved_to_repos_relpaths, + svn_client_conflict_option_t *option, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( + possible_moved_to_repos_relpaths, option, result_pool, scratch_pool)); +} + +static svn_error_t * +set_wc_move_target(const char **new_hash_key, + apr_hash_t *wc_move_targets, + int preferred_move_target_idx, + const char *victim_abspath, + apr_pool_t *scratch_pool) +{ apr_array_header_t *move_target_repos_relpaths; svn_sort__item_t item; const char *move_target_repos_relpath; apr_hash_index_t *hi; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); - - victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Setting a move target requires details " - "for tree conflict at '%s' to be fetched " - "from the repository first"), - svn_dirent_local_style(victim_abspath, - scratch_pool)); - if (preferred_move_target_idx < 0 || - preferred_move_target_idx >= apr_hash_count(details->wc_move_targets)) + preferred_move_target_idx >= apr_hash_count(wc_move_targets)) return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, _("Index '%d' is out of bounds of the possible " "move target list for '%s'"), @@ -10530,15 +10636,14 @@ svn_client_conflict_option_set_moved_to_ scratch_pool)); /* Translate the index back into a hash table key. */ - move_target_repos_relpaths = - svn_sort__hash(details->wc_move_targets, - svn_sort_compare_items_as_paths, - scratch_pool); + move_target_repos_relpaths = svn_sort__hash(wc_move_targets, + svn_sort_compare_items_as_paths, + scratch_pool); item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx, svn_sort__item_t); move_target_repos_relpath = item.key; /* Find our copy of the hash key and remember the user's preference. */ - for (hi = apr_hash_first(scratch_pool, details->wc_move_targets); + for (hi = apr_hash_first(scratch_pool, wc_move_targets); hi != NULL; hi = apr_hash_next(hi)) { @@ -10546,15 +10651,7 @@ svn_client_conflict_option_set_moved_to_ if (strcmp(move_target_repos_relpath, repos_relpath) == 0) { - details->move_target_repos_relpath = repos_relpath; - /* Update option description. */ - SVN_ERR(describe_incoming_move_merge_conflict_option( - &option->description, - conflict, ctx, - details, - conflict->pool, - scratch_pool)); - + *new_hash_key = repos_relpath; return SVN_NO_ERROR; } } @@ -10568,7 +10665,108 @@ svn_client_conflict_option_set_moved_to_ } svn_error_t * -svn_client_conflict_option_get_moved_to_abspath_candidates( +svn_client_conflict_option_set_moved_to_repos_relpath2( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_t *conflict = option->conflict; + const char *victim_abspath; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + svn_client_conflict_option_id_t id; + + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge) + return SVN_NO_ERROR; /* We cannot operate on this option. Nothing to do. */ + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + 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); + + 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; + + details = conflict->tree_conflict_local_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath, + details->wc_move_targets, + preferred_move_target_idx, + victim_abspath, scratch_pool)); + details->wc_move_target_idx = 0; + + /* Update option description. */ + SVN_ERR(conflict_tree_get_description_local_missing( + &option->description, conflict, ctx, + conflict->pool, scratch_pool)); + } + else + { + struct conflict_tree_incoming_delete_details *details; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath, + details->wc_move_targets, + preferred_move_target_idx, + victim_abspath, scratch_pool)); + details->wc_move_target_idx = 0; + + /* Update option description. */ + SVN_ERR(describe_incoming_move_merge_conflict_option( + &option->description, + conflict, ctx, + details, + conflict->pool, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_conflict_option_set_moved_to_repos_relpath( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_set_moved_to_repos_relpath2(option, + preferred_move_target_idx, ctx, scratch_pool)); +} + +svn_error_t * +svn_client_conflict_option_get_moved_to_abspath_candidates2( apr_array_header_t **possible_moved_to_abspaths, svn_client_conflict_option_t *option, apr_pool_t *result_pool, @@ -10580,17 +10778,20 @@ svn_client_conflict_option_get_moved_to_ svn_wc_conflict_action_t incoming_change; svn_wc_conflict_reason_t local_change; int i; + svn_client_conflict_option_id_t id; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_local_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_sibling_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_sibling_move_dir_merge); + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge) + { + /* We cannot operate on this option. */ + *possible_moved_to_abspaths = NULL; + return NULL; + } victim_abspath = svn_client_conflict_get_local_abspath(conflict); operation = svn_client_conflict_get_operation(conflict); @@ -10605,7 +10806,7 @@ svn_client_conflict_option_get_moved_to_ details = conflict->tree_conflict_local_details; if (details == NULL || - (details->moved_to_abspath == NULL && details->wc_siblings == NULL)) + (details->wc_move_targets == NULL && details->wc_siblings == NULL)) return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, _("Getting a list of possible move siblings " "requires details for tree conflict at '%s' " @@ -10615,9 +10816,22 @@ svn_client_conflict_option_get_moved_to_ *possible_moved_to_abspaths = apr_array_make(result_pool, 1, sizeof (const char *)); - if (details->moved_to_abspath) - APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = - apr_pstrdup(result_pool, details->moved_to_abspath); + if (details->wc_move_targets) + { + apr_array_header_t *move_target_wc_abspaths; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + for (i = 0; i < move_target_wc_abspaths->nelts; i++) + { + const char *moved_to_abspath; + + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i, + const char *); + APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, moved_to_abspath); + } + } /* ### Siblings are actually 'corresponding nodes', not 'move targets'. ### But we provide them here to avoid another API function. */ @@ -10671,7 +10885,23 @@ svn_client_conflict_option_get_moved_to_ } svn_error_t * -svn_client_conflict_option_set_moved_to_abspath( +svn_client_conflict_option_get_moved_to_abspath_candidates( + apr_array_header_t **possible_moved_to_abspaths, + svn_client_conflict_option_t *option, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_get_moved_to_abspath_candidates2( + possible_moved_to_abspaths, option, result_pool, scratch_pool)); +} + +svn_error_t * +svn_client_conflict_option_set_moved_to_abspath2( svn_client_conflict_option_t *option, int preferred_move_target_idx, svn_client_ctx_t *ctx, @@ -10682,17 +10912,16 @@ svn_client_conflict_option_set_moved_to_ svn_wc_operation_t operation; svn_wc_conflict_action_t incoming_change; svn_wc_conflict_reason_t local_change; + svn_client_conflict_option_id_t id; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_local_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_sibling_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_sibling_move_dir_merge); + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge) + return NULL; /* We cannot operate on this option. Nothing to do. */ victim_abspath = svn_client_conflict_get_local_abspath(conflict); operation = svn_client_conflict_get_operation(conflict); @@ -10714,35 +10943,63 @@ svn_client_conflict_option_set_moved_to_ scratch_pool)); details = conflict->tree_conflict_local_details; - if (details == NULL || details->wc_siblings == NULL) + if (details == NULL || (details->wc_siblings == NULL && + details->wc_move_targets == NULL)) return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Setting a move sibling requires details " + _("Setting a move target requires details " "for tree conflict at '%s' to be fetched " "from the repository first"), svn_dirent_local_style(victim_abspath, scratch_pool)); - if (preferred_move_target_idx < 0 || - preferred_move_target_idx > details->wc_siblings->nelts) - return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, - _("Index '%d' is out of bounds of the possible " - "move sibling list for '%s'"), - preferred_move_target_idx, - svn_dirent_local_style(victim_abspath, - scratch_pool)); - /* Record the user's preference. */ - details->preferred_sibling_idx = preferred_move_target_idx; + if (details->wc_siblings) + { + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > details->wc_siblings->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the " + "possible move sibling list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + /* Record the user's preference. */ + details->preferred_sibling_idx = preferred_move_target_idx; - /* Update option description. */ - preferred_sibling = APR_ARRAY_IDX(details->wc_siblings, - details->preferred_sibling_idx, - const char *); - option->description = - apr_psprintf( - conflict->pool, _("apply changes to '%s'"), - svn_dirent_local_style( - svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling), - scratch_pool)); + /* Update option description. */ + preferred_sibling = APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *); + option->description = + apr_psprintf( + conflict->pool, _("apply changes to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling), + scratch_pool)); + } + else if (details->wc_move_targets) + { + apr_array_header_t *move_target_wc_abspaths; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > move_target_wc_abspaths->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the possible " + "move target list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + /* Record the user's preference. */ + details->wc_move_target_idx = preferred_move_target_idx; + + /* Update option description. */ + SVN_ERR(conflict_tree_get_description_local_missing( + &option->description, conflict, ctx, + conflict->pool, scratch_pool)); + } } else { @@ -10785,6 +11042,22 @@ svn_client_conflict_option_set_moved_to_ } svn_error_t * +svn_client_conflict_option_set_moved_to_abspath( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_set_moved_to_abspath2(option, + preferred_move_target_idx, ctx, scratch_pool)); +} + +svn_error_t * svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options, svn_client_conflict_t *conflict, svn_client_ctx_t *ctx, Modified: subversion/branches/1.11.x/subversion/svn/conflict-callbacks.c URL: http://svn.apache.org/viewvc/subversion/branches/1.11.x/subversion/svn/conflict-callbacks.c?rev=1841732&r1=1841731&r2=1841732&view=diff ============================================================================== --- subversion/branches/1.11.x/subversion/svn/conflict-callbacks.c (original) +++ subversion/branches/1.11.x/subversion/svn/conflict-callbacks.c Sun Sep 23 09:43:41 2018 @@ -1534,22 +1534,14 @@ build_tree_conflict_options( id != svn_client_conflict_option_accept_current_wc_state) *all_options_are_dumb = FALSE; - if (id == svn_client_conflict_option_incoming_move_file_text_merge || - id == svn_client_conflict_option_incoming_move_dir_merge) - { + if (*possible_moved_to_repos_relpaths == NULL) SVN_ERR( - svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( possible_moved_to_repos_relpaths, builtin_option, result_pool, iterpool)); - SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( - possible_moved_to_abspaths, builtin_option, - result_pool, iterpool)); - } - else if (id == svn_client_conflict_option_local_move_file_text_merge || - id == svn_client_conflict_option_local_move_dir_merge || - id == svn_client_conflict_option_sibling_move_file_text_merge || - id == svn_client_conflict_option_sibling_move_dir_merge) - SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + + if (*possible_moved_to_abspaths == NULL) + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2( possible_moved_to_abspaths, builtin_option, result_pool, iterpool)); } @@ -1682,6 +1674,69 @@ prompt_move_target_path(int *preferred_m return SVN_NO_ERROR; } +static svn_error_t * +find_conflict_option_with_repos_move_targets( + svn_client_conflict_option_t **option_with_move_targets, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + apr_array_header_t *possible_moved_to_repos_relpaths = NULL; + + *option_with_move_targets = NULL; + + for (i = 0; i < options->nelts; i++) + { + svn_client_conflict_option_t *option; + + svn_pool_clear(iterpool); + option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); + SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( + &possible_moved_to_repos_relpaths, option, iterpool, iterpool)); + if (possible_moved_to_repos_relpaths) + { + *option_with_move_targets = option; + break; + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +static svn_error_t * +find_conflict_option_with_working_copy_move_targets( + svn_client_conflict_option_t **option_with_move_targets, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + apr_array_header_t *possible_moved_to_abspaths = NULL; + + *option_with_move_targets = NULL; + + for (i = 0; i < options->nelts; i++) + { + svn_client_conflict_option_t *option; + + svn_pool_clear(iterpool); + option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2( + &possible_moved_to_abspaths, option, scratch_pool, + iterpool)); + if (possible_moved_to_abspaths) + { + *option_with_move_targets = option; + break; + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + /* Ask the user what to do about the tree conflict described by CONFLICT * and either resolve the conflict accordingly or postpone resolution. * SCRATCH_POOL is used for temporary allocations. */ @@ -1809,7 +1864,7 @@ handle_tree_conflict(svn_boolean_t *reso { int preferred_move_target_idx; apr_array_header_t *options; - svn_client_conflict_option_t *conflict_option; + svn_client_conflict_option_t *option; SVN_ERR(prompt_move_target_path(&preferred_move_target_idx, possible_moved_to_repos_relpaths, @@ -1822,22 +1877,12 @@ handle_tree_conflict(svn_boolean_t *reso ctx, iterpool, iterpool)); - conflict_option = - svn_client_conflict_option_find_by_id( - options, - svn_client_conflict_option_incoming_move_file_text_merge); - if (conflict_option == NULL) + SVN_ERR(find_conflict_option_with_repos_move_targets( + &option, options, iterpool)); + if (option) { - conflict_option = - svn_client_conflict_option_find_by_id( - options, svn_client_conflict_option_incoming_move_dir_merge); - } - - if (conflict_option) - { - SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath( - conflict_option, preferred_move_target_idx, - ctx, iterpool)); + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath2( + option, preferred_move_target_idx, ctx, iterpool)); repos_move_target_chosen = TRUE; wc_move_target_chosen = FALSE; @@ -1863,7 +1908,7 @@ handle_tree_conflict(svn_boolean_t *reso { int preferred_move_target_idx; apr_array_header_t *options; - svn_client_conflict_option_t *conflict_option; + svn_client_conflict_option_t *option; SVN_ERR(prompt_move_target_path(&preferred_move_target_idx, possible_moved_to_abspaths, TRUE, @@ -1875,50 +1920,12 @@ handle_tree_conflict(svn_boolean_t *reso ctx, iterpool, iterpool)); - conflict_option = - svn_client_conflict_option_find_by_id( - options, - svn_client_conflict_option_incoming_move_file_text_merge); - if (conflict_option == NULL) - { - conflict_option = - svn_client_conflict_option_find_by_id( - options, - svn_client_conflict_option_local_move_file_text_merge); - } - if (conflict_option == NULL) - { - conflict_option = - svn_client_conflict_option_find_by_id( - options, - svn_client_conflict_option_local_move_dir_merge); - } - if (conflict_option == NULL) - { - conflict_option = - svn_client_conflict_option_find_by_id( - options, - svn_client_conflict_option_sibling_move_file_text_merge); - } - if (conflict_option == NULL) - { - conflict_option = - svn_client_conflict_option_find_by_id( - options, - svn_client_conflict_option_sibling_move_dir_merge); - } - if (conflict_option == NULL) - { - conflict_option = - svn_client_conflict_option_find_by_id( - options, svn_client_conflict_option_incoming_move_dir_merge); - } - - if (conflict_option) + SVN_ERR(find_conflict_option_with_working_copy_move_targets( + &option, options, iterpool)); + if (option) { - SVN_ERR(svn_client_conflict_option_set_moved_to_abspath( - conflict_option, preferred_move_target_idx, ctx, - iterpool)); + SVN_ERR(svn_client_conflict_option_set_moved_to_abspath2( + option, preferred_move_target_idx, ctx, iterpool)); wc_move_target_chosen = TRUE; /* Update option description. */ Modified: subversion/branches/1.11.x/subversion/tests/libsvn_client/conflicts-test.c URL: http://svn.apache.org/viewvc/subversion/branches/1.11.x/subversion/tests/libsvn_client/conflicts-test.c?rev=1841732&r1=1841731&r2=1841732&view=diff ============================================================================== --- subversion/branches/1.11.x/subversion/tests/libsvn_client/conflicts-test.c (original) +++ subversion/branches/1.11.x/subversion/tests/libsvn_client/conflicts-test.c Sun Sep 23 09:43:41 2018 @@ -5700,6 +5700,185 @@ test_cherry_pick_post_move_edit_dir(cons return SVN_NO_ERROR; } +static svn_error_t * +test_local_missing_abiguous_moves(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b)); + svn_opt_revision_t opt_rev; + svn_client_ctx_t *ctx; + svn_client_conflict_t *conflict; + apr_array_header_t *options; + svn_client_conflict_option_t *option; + apr_array_header_t *possible_moved_to_repos_relpaths; + apr_array_header_t *possible_moved_to_abspaths; + struct status_baton sb; + struct svn_client_status_t *status; + svn_stringbuf_t *buf; + + SVN_ERR(svn_test__sandbox_create(b, "local_missing_ambiguous_moves", opts, + pool)); + + SVN_ERR(sbox_add_and_commit_greek_tree(b)); /* r1 */ + + /* Create a copy of node "A" (the "trunk") to "A1" (the "branch"). */ + SVN_ERR(sbox_wc_copy(b, "A", "A1")); + SVN_ERR(sbox_wc_commit(b, "")); /* r2 */ + + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + /* Copy a file across branch boundaries (gives ambiguous WC targets later). */ + SVN_ERR(sbox_wc_copy(b, "A/mu", "A1/mu-copied-from-A")); + /* Create an ambiguous move with the "trunk". */ + SVN_ERR(sbox_wc_copy(b, "A/mu", "A/mu-copied")); + SVN_ERR(sbox_wc_move(b, "A/mu", "A/mu-moved")); + SVN_ERR(sbox_wc_commit(b, "")); /* r3 */ + + /* Modify the moved file on the "branch". */ + SVN_ERR(sbox_file_write(b, "A1/mu", "Modified content." APR_EOL_STR)); + SVN_ERR(sbox_wc_commit(b, "")); /* r4 */ + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + + /* Merge "A1" ("branch") into "A" ("trunk"). */ + opt_rev.kind = svn_opt_revision_head; + opt_rev.value.number = SVN_INVALID_REVNUM; + SVN_ERR(svn_test__create_client_ctx(&ctx, b, pool)); + SVN_ERR(svn_client_merge_peg5(svn_path_url_add_component2(b->repos_url, "A1", + pool), + NULL, &opt_rev, sbox_wc_path(b, "A"), + svn_depth_infinity, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + NULL, ctx, pool)); + + SVN_ERR(svn_client_conflict_get(&conflict, sbox_wc_path(b, "A/mu"), + ctx, b->pool, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_local_move_file_text_merge, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, conflict, + ctx, b->pool, + b->pool)); + option = svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_local_move_file_text_merge); + SVN_TEST_ASSERT(option != NULL); + + /* + * Possible repository destinations for moved-away 'A/mu' are: + * (1): '^/A/mu-copied' + * (2): '^/A/mu-moved' + * (3): '^/A1/mu-copied-from-A' + */ + SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + &possible_moved_to_repos_relpaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_repos_relpaths->nelts, 3); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 0, const char *), + "A/mu-copied"); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 1, const char *), + "A/mu-moved"); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 2, const char *), + "A1/mu-copied-from-A"); + + /* Move target for "A/mu-copied" (selected by default) is not ambiguous. */ + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 1); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A/mu-copied")); + + /* Move target for "A/mu-moved" is not ambiguous. */ + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 1, + ctx, b->pool)); + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 1); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A/mu-moved")); + + /* Select move target "A1/mu-copied-from-A". */ + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 2, + ctx, b->pool)); + + /* + * Possible working copy destinations for moved-away 'A/mu' are: + * (1): 'A/mu-copied-from-A' + * (2): 'A1/mu-copied-from-A' + */ + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 2); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A/mu-copied-from-A")); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 1, const char *), + sbox_wc_path(b, "A1/mu-copied-from-A")); + + /* Select move target "A/mu-moved". */ + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 1, + ctx, b->pool)); + + /* Resolve the tree conflict. */ + SVN_ERR(svn_client_conflict_tree_resolve_by_id( + conflict, + svn_client_conflict_option_local_move_file_text_merge, ctx, + b->pool)); + + /* The node "A/mu" should no longer exist. */ + SVN_TEST_ASSERT_ERROR(svn_client_conflict_get(&conflict, + sbox_wc_path(b, "A/mu"), + ctx, pool, pool), + SVN_ERR_WC_PATH_NOT_FOUND); + + /* Ensure that the merged file has the expected status. */ + opt_rev.kind = svn_opt_revision_working; + sb.result_pool = b->pool; + SVN_ERR(svn_client_status6(NULL, ctx, sbox_wc_path(b, "A/mu-moved"), + &opt_rev, svn_depth_unknown, TRUE, TRUE, + TRUE, TRUE, FALSE, TRUE, NULL, + status_func, &sb, b->pool)); + status = sb.status; + SVN_TEST_ASSERT(status->kind == svn_node_file); + SVN_TEST_ASSERT(status->versioned); + SVN_TEST_ASSERT(!status->conflicted); + SVN_TEST_ASSERT(status->node_status == svn_wc_status_modified); + SVN_TEST_ASSERT(status->text_status == svn_wc_status_modified); + SVN_TEST_ASSERT(status->prop_status == svn_wc_status_none); + SVN_TEST_ASSERT(!status->copied); + SVN_TEST_ASSERT(!status->switched); + SVN_TEST_ASSERT(!status->file_external); + SVN_TEST_ASSERT(status->moved_from_abspath == NULL); + SVN_TEST_ASSERT(status->moved_to_abspath == NULL); + + /* And it should have expected contents. */ + SVN_ERR(svn_stringbuf_from_file2(&buf, sbox_wc_path(b, "A/mu-moved"), + pool)); + SVN_TEST_STRING_ASSERT(buf->data, "Modified content." APR_EOL_STR); + + return SVN_NO_ERROR; +} + /* ========================================================================== */ @@ -5798,6 +5977,8 @@ static struct svn_test_descriptor_t test "do not suggest unrelated move targets (#4766)"), SVN_TEST_OPTS_PASS(test_cherry_pick_post_move_edit_dir, "cherry-pick edit from moved directory"), + SVN_TEST_OPTS_PASS(test_local_missing_abiguous_moves, + "local missing conflict with ambiguous moves"), SVN_TEST_NULL };