summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/conflicts.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/conflicts.c')
-rw-r--r--subversion/libsvn_client/conflicts.c3201
1 files changed, 2723 insertions, 478 deletions
diff --git a/subversion/libsvn_client/conflicts.c b/subversion/libsvn_client/conflicts.c
index d1a2ae2..920c55e 100644
--- a/subversion/libsvn_client/conflicts.c
+++ b/subversion/libsvn_client/conflicts.c
@@ -255,7 +255,7 @@ struct repos_move_info {
/* The revision in which this move was committed. */
svn_revnum_t rev;
- /* The author who commited the revision in which this move was committed. */
+ /* The author who committed the revision in which this move was committed. */
const char *rev_author;
/* The repository relpath the node was moved from in this revision. */
@@ -2350,6 +2350,8 @@ struct conflict_tree_local_missing_details
/* 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,21 +2359,49 @@ struct conflict_tree_local_missing_details
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 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. */
apr_array_header_t *sibling_moves;
- /* If not NULL, this is the move target abspath. */
- const char *moved_to_abspath;
+ /* List of nodes in the WC which are suitable merge targets for changes
+ * merged from any moved sibling. Array elements are 'const char *'
+ * absolute paths of working copy nodes. This array contains multiple
+ * elements only if ambiguous matches were found in the WC. */
+ apr_array_header_t *wc_siblings;
+ int preferred_sibling_idx;
};
static svn_error_t *
@@ -2590,6 +2620,168 @@ find_moves_in_natural_history(apr_array_header_t **moves,
return SVN_NO_ERROR;
}
+static svn_error_t *
+collect_sibling_move_candidates(apr_array_header_t *candidates,
+ const char *victim_abspath,
+ svn_node_kind_t victim_kind,
+ struct repos_move_info *move,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *basename;
+ apr_array_header_t *abspaths;
+ int i;
+
+ basename = svn_relpath_basename(move->moved_from_repos_relpath, scratch_pool);
+ SVN_ERR(svn_wc__find_working_nodes_with_basename(&abspaths, victim_abspath,
+ basename, victim_kind,
+ ctx->wc_ctx, result_pool,
+ scratch_pool));
+ apr_array_cat(candidates, abspaths);
+
+ if (move->next)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < move->next->nelts; i++)
+ {
+ struct repos_move_info *next_move;
+ next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
+ SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath,
+ victim_kind, next_move, ctx,
+ result_pool, iterpool));
+ svn_pool_clear(iterpool);
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ 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)
+{
+ 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);
+ }
+
+ if (move->next)
+ {
+ 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,
@@ -2603,12 +2795,15 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
svn_revnum_t old_rev;
svn_revnum_t new_rev;
svn_revnum_t deleted_rev;
+ svn_node_kind_t old_kind;
+ svn_node_kind_t new_kind;
const char *deleted_rev_author;
svn_node_kind_t replacing_node_kind;
const char *deleted_basename;
struct conflict_tree_local_missing_details *details;
apr_array_header_t *moves = NULL;
apr_array_header_t *sibling_moves = NULL;
+ apr_array_header_t *wc_siblings = NULL;
const char *related_repos_relpath;
svn_revnum_t related_peg_rev;
const char *repos_root_url;
@@ -2619,10 +2814,10 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
svn_revnum_t end_rev;
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
- &old_repos_relpath, &old_rev, NULL, conflict,
+ &old_repos_relpath, &old_rev, &old_kind, conflict,
scratch_pool, scratch_pool));
SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
- &new_repos_relpath, &new_rev, NULL, conflict,
+ &new_repos_relpath, &new_rev, &new_kind, conflict,
scratch_pool, scratch_pool));
/* Scan the conflict victim's parent's log to find a revision which
@@ -2638,6 +2833,11 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
scratch_pool,
scratch_pool));
+ /* If the parent is not part of the repository-side tree checked out
+ * into this working copy, then bail. We do not support this case yet. */
+ if (parent_peg_rev == SVN_INVALID_REVNUM)
+ return SVN_NO_ERROR;
+
/* Pick the younger incoming node as our 'related node' which helps
* pin-pointing the deleted conflict victim in history. */
related_repos_relpath =
@@ -2693,6 +2893,9 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
{
const char *victim_abspath;
svn_node_kind_t related_node_kind;
+ apr_array_header_t *candidates;
+ int i;
+ apr_pool_t *iterpool;
/* ### The following describes all moves in terms of forward-merges,
* should do we something else for reverse-merges? */
@@ -2726,7 +2929,81 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
if (sibling_moves == NULL)
return SVN_NO_ERROR;
- /* ## TODO: Find the missing node in the WC. */
+ /* Find the missing node in the WC. In theory, this requires tracing
+ * back history of every node in the WC to check for a YCA with the
+ * conflict victim. This operation would obviously be quite expensive.
+ *
+ * However, assuming that the victim was not moved in the merge target,
+ * we can take a short-cut: The basename of the node cannot have changed,
+ * so we can limit history tracing to nodes with a matching basename.
+ *
+ * This approach solves the conflict case where an edit to a file which
+ * was moved on one branch is cherry-picked to another branch where the
+ * corresponding file has not been moved (yet). It does not solve move
+ * vs. move conflicts, but such conflicts are not yet supported by the
+ * resolver anyway and are hard to solve without server-side support. */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < sibling_moves->nelts; i++)
+ {
+ struct repos_move_info *move;
+ int j;
+
+ svn_pool_clear(iterpool);
+
+ move = APR_ARRAY_IDX(sibling_moves, i, struct repos_move_info *);
+ candidates = apr_array_make(iterpool, 1, sizeof(const char *));
+ SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath,
+ old_rev < new_rev
+ ? new_kind : old_kind,
+ move, ctx, iterpool,
+ iterpool));
+
+ /* Determine whether a candidate node shares a YCA with the victim. */
+ for (j = 0; j < candidates->nelts; j++)
+ {
+ const char *candidate_abspath;
+ const char *candidate_repos_relpath;
+ svn_revnum_t candidate_revision;
+ svn_error_t *err;
+
+ candidate_abspath = APR_ARRAY_IDX(candidates, j, const char *);
+ SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
+ &candidate_repos_relpath,
+ NULL, NULL, NULL, NULL,
+ ctx->wc_ctx,
+ candidate_abspath,
+ FALSE,
+ iterpool, iterpool));
+ err = find_yca(&yca_loc,
+ old_rev < new_rev
+ ? new_repos_relpath : old_repos_relpath,
+ old_rev < new_rev ? new_rev : old_rev,
+ 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)
+ {
+ if (wc_siblings == NULL)
+ wc_siblings = apr_array_make(conflict->pool, 1,
+ sizeof(const char *));
+ APR_ARRAY_PUSH(wc_siblings, const char *) =
+ apr_pstrdup(conflict->pool, candidate_abspath);
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
}
details = apr_pcalloc(conflict->pool, sizeof(*details));
@@ -2737,8 +3014,83 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
deleted_basename,
conflict->pool);
details->moves = moves;
- details->sibling_moves = sibling_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,
+ new_kind,
+ 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_move_targets && 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);
+ if (wc_abspaths->nelts == 1)
+ {
+ svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
+
+ if (kind == svn_node_file)
+ conflict->recommended_option_id =
+ svn_client_conflict_option_local_move_file_text_merge;
+ else if (kind == svn_node_dir)
+ conflict->recommended_option_id =
+ svn_client_conflict_option_local_move_dir_merge;
+ }
+ }
+ else if (details->wc_siblings && details->wc_siblings->nelts == 1)
+ {
+ svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
+
+ if (kind == svn_node_file)
+ conflict->recommended_option_id =
+ svn_client_conflict_option_sibling_move_file_text_merge;
+ else if (kind == svn_node_dir)
+ conflict->recommended_option_id =
+ svn_client_conflict_option_sibling_move_dir_merge;
+ }
+
conflict->tree_conflict_local_details = details;
return SVN_NO_ERROR;
@@ -4647,133 +4999,6 @@ get_incoming_delete_details_for_reverse_addition(
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;
-}
-
static svn_error_t *
init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
svn_client_conflict_t *conflict,
@@ -4785,11 +5010,9 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
svn_node_kind_t victim_node_kind;
const char *incoming_new_repos_relpath;
svn_revnum_t incoming_new_pegrev;
- svn_wc_operation_t operation;
victim_abspath = svn_client_conflict_get_local_abspath(conflict);
victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
- operation = svn_client_conflict_get_operation(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,
@@ -4815,11 +5038,8 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
get_moved_to_repos_relpath(details, scratch_pool);
details->wc_move_target_idx = 0;
- /* If only one move target exists after an update or switch,
- * recommend a resolution option which follows the incoming move. */
- if (apr_hash_count(details->wc_move_targets) == 1 &&
- (operation == svn_wc_operation_update ||
- operation == svn_wc_operation_switch))
+ /* 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;
@@ -4832,7 +5052,10 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
/* Only one of these will be present for any given conflict. */
svn_client_conflict_option_incoming_move_file_text_merge,
svn_client_conflict_option_incoming_move_dir_merge,
- svn_client_conflict_option_local_move_file_text_merge
+ svn_client_conflict_option_local_move_file_text_merge,
+ svn_client_conflict_option_local_move_dir_merge,
+ svn_client_conflict_option_sibling_move_file_text_merge,
+ svn_client_conflict_option_sibling_move_dir_merge,
};
apr_array_header_t *options;
@@ -4889,6 +5112,7 @@ conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict,
const char *parent_repos_relpath;
svn_revnum_t parent_peg_rev;
svn_revnum_t deleted_rev;
+ svn_revnum_t end_rev;
const char *deleted_rev_author;
svn_node_kind_t replacing_node_kind;
apr_array_header_t *moves;
@@ -4921,12 +5145,15 @@ conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict,
related_peg_rev = SVN_INVALID_REVNUM;
}
+ end_rev = (new_kind == svn_node_none ? 0 : old_rev);
+ if (end_rev >= parent_peg_rev)
+ end_rev = (parent_peg_rev > 0 ? parent_peg_rev - 1 : 0);
+
SVN_ERR(find_revision_for_suspected_deletion(
&deleted_rev, &deleted_rev_author, &replacing_node_kind,
&moves, conflict,
svn_dirent_basename(conflict->local_abspath, scratch_pool),
- parent_repos_relpath, parent_peg_rev,
- new_kind == svn_node_none ? 0 : old_rev,
+ parent_repos_relpath, parent_peg_rev, end_rev,
related_repos_relpath, related_peg_rev,
ctx, conflict->pool, scratch_pool));
if (deleted_rev == SVN_INVALID_REVNUM)
@@ -5952,8 +6179,11 @@ describe_incoming_edit_list_modified_revs(apr_array_header_t *edits,
{
if (i == edits->nelts - (max_revs_to_display / 2))
s = apr_psprintf(result_pool,
- _("%s\n [%d revisions omitted for "
- "brevity],\n"),
+ Q_("%s\n [%d revision omitted for "
+ "brevity],\n",
+ "%s\n [%d revisions omitted for "
+ "brevity],\n",
+ num_revs_to_skip),
s, num_revs_to_skip);
s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
@@ -6725,8 +6955,9 @@ resolve_merge_incoming_added_file_text_update(
/* Revert the path in order to restore the repository's line of
* history, which is part of the BASE tree. This revert operation
* is why are being careful about not losing the temporary copy. */
- err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_empty,
+ err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_empty,
FALSE, NULL, TRUE, FALSE,
+ TRUE /*added_keep_local*/,
NULL, NULL, /* no cancellation */
ctx->notify_func2, ctx->notify_baton2,
scratch_pool);
@@ -7543,7 +7774,6 @@ merge_newly_added_dir(const char *added_repos_relpath,
diff_processor = processor;
if (reverse_merge)
diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
- NULL,
scratch_pool);
/* Filter the first path component using a filter processor, until we fixed
@@ -7726,8 +7956,9 @@ resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option,
* files with files from the repository is impossible because there is
* no known merge base. No unversioned data will be lost, and any
* differences to files in the repository will show up in 'svn diff'. */
- err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_infinity,
+ err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_infinity,
FALSE, NULL, TRUE, TRUE /* metadata_only */,
+ TRUE /*added_keep_local*/,
NULL, NULL, /* no cancellation */
ctx->notify_func2, ctx->notify_baton2,
scratch_pool);
@@ -7754,35 +7985,6 @@ resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option,
return SVN_NO_ERROR;
}
-/* A baton for notification_adjust_func(). */
-struct notification_adjust_baton
-{
- svn_wc_notify_func2_t inner_func;
- void *inner_baton;
- const char *checkout_abspath;
- const char *final_abspath;
-};
-
-/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
- * baton is BATON->inner_baton) and adjusts the notification paths that
- * start with BATON->checkout_abspath to start instead with
- * BATON->final_abspath. */
-static void
-notification_adjust_func(void *baton,
- const svn_wc_notify_t *notify,
- apr_pool_t *pool)
-{
- struct notification_adjust_baton *nb = baton;
- svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
- const char *relpath;
-
- relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
- inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
-
- if (nb->inner_func)
- nb->inner_func(nb->inner_baton, inner_notify, pool);
-}
-
/* Resolve a dir/dir "incoming add vs local obstruction" tree conflict by
* replacing the local directory with the incoming directory.
* If MERGE_DIRS is set, also merge the directories after replacing. */
@@ -7801,14 +8003,8 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
svn_revnum_t incoming_new_pegrev;
const char *local_abspath;
const char *lock_abspath;
- const char *tmpdir_abspath, *tmp_abspath;
svn_error_t *err;
- svn_revnum_t copy_src_revnum;
- svn_opt_revision_t copy_src_peg_revision;
svn_boolean_t timestamp_sleep;
- svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
- void *old_notify_baton2 = ctx->notify_baton2;
- struct notification_adjust_baton nb;
local_abspath = svn_client_conflict_get_local_abspath(conflict);
@@ -7829,46 +8025,6 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
if (corrected_url)
url = corrected_url;
-
- /* Find a temporary location in which to check out the copy source. */
- SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, local_abspath,
- scratch_pool, scratch_pool));
-
- SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
- svn_io_file_del_on_close,
- scratch_pool, scratch_pool));
-
- /* Make a new checkout of the requested source. While doing so,
- * resolve copy_src_revnum to an actual revision number in case it
- * was until now 'invalid' meaning 'head'. Ask this function not to
- * sleep for timestamps, by passing a sleep_needed output param.
- * Send notifications for all nodes except the root node, and adjust
- * them to refer to the destination rather than this temporary path. */
-
- nb.inner_func = ctx->notify_func2;
- nb.inner_baton = ctx->notify_baton2;
- nb.checkout_abspath = tmp_abspath;
- nb.final_abspath = local_abspath;
- ctx->notify_func2 = notification_adjust_func;
- ctx->notify_baton2 = &nb;
-
- copy_src_peg_revision.kind = svn_opt_revision_number;
- copy_src_peg_revision.value.number = incoming_new_pegrev;
-
- err = svn_client__checkout_internal(&copy_src_revnum, &timestamp_sleep,
- url, tmp_abspath,
- &copy_src_peg_revision,
- &copy_src_peg_revision,
- svn_depth_infinity,
- TRUE, /* we want to ignore externals */
- FALSE, /* we don't allow obstructions */
- ra_session, ctx, scratch_pool);
-
- ctx->notify_func2 = old_notify_func2;
- ctx->notify_baton2 = old_notify_baton2;
-
- SVN_ERR(err);
-
/* ### The following WC modifications should be atomic. */
SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
@@ -7885,31 +8041,11 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
if (err)
goto unlock_wc;
- /* Schedule dst_path for addition in parent, with copy history.
- Don't send any notification here.
- Then remove the temporary checkout's .svn dir in preparation for
- moving the rest of it into the final destination. */
- err = svn_wc_copy3(ctx->wc_ctx, tmp_abspath, local_abspath,
- TRUE /* metadata_only */,
- NULL, NULL, /* don't allow user to cancel here */
- NULL, NULL, scratch_pool);
- if (err)
- goto unlock_wc;
-
- err = svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
- FALSE, scratch_pool, scratch_pool);
- if (err)
- goto unlock_wc;
- err = svn_wc_remove_from_revision_control2(ctx->wc_ctx,
- tmp_abspath,
- FALSE, FALSE,
- NULL, NULL, /* don't cancel */
- scratch_pool);
- if (err)
- goto unlock_wc;
-
- /* Move the temporary disk tree into place. */
- err = svn_io_file_rename2(tmp_abspath, local_abspath, FALSE, scratch_pool);
+ err = svn_client__repos_to_wc_copy_by_editor(&timestamp_sleep,
+ svn_node_dir,
+ url, incoming_new_pegrev,
+ local_abspath,
+ ra_session, ctx, scratch_pool);
if (err)
goto unlock_wc;
@@ -8346,7 +8482,9 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
apr_pool_t *scratch_pool)
{
svn_client_conflict_option_id_t option_id;
- const char *local_abspath;
+ const char *victim_abspath;
+ const char *merge_source_abspath;
+ svn_wc_conflict_reason_t local_change;
svn_wc_operation_t operation;
const char *lock_abspath;
svn_error_t *err;
@@ -8372,7 +8510,8 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
const char *moved_to_abspath;
const char *incoming_abspath = NULL;
- local_abspath = svn_client_conflict_get_local_abspath(conflict);
+ victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
operation = svn_client_conflict_get_operation(conflict);
details = conflict->tree_conflict_incoming_details;
if (details == NULL || details->moves == NULL)
@@ -8380,20 +8519,20 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
_("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(local_abspath,
+ 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_file_text_merge ||
option_id ==
- svn_client_conflict_option_incoming_move_dir_merge);
+ svn_client_conflict_option_both_moved_file_move_merge);
SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
conflict, scratch_pool,
@@ -8408,7 +8547,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
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, local_abspath,
+ 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,
@@ -8438,21 +8577,41 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
details->wc_move_target_idx,
const char *);
+ if (local_change == svn_wc_conflict_reason_missing)
+ {
+ /* This is an incoming move vs local move conflict.
+ * Merge from the local move's target location to the
+ * incoming move's target location. */
+ struct conflict_tree_local_missing_details *local_details;
+ apr_array_header_t *moves;
+
+ local_details = conflict->tree_conflict_local_details;
+ moves = svn_hash_gets(local_details->wc_move_targets,
+ local_details->move_target_repos_relpath);
+ merge_source_abspath =
+ APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, const char *);
+ }
+ else
+ merge_source_abspath = victim_abspath;
+
/* ### 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,
+ svn_dirent_get_longest_ancestor(victim_abspath,
moved_to_abspath,
scratch_pool),
scratch_pool, scratch_pool));
- err = verify_local_state_for_incoming_delete(conflict, option, ctx,
- scratch_pool);
- if (err)
- goto unlock_wc;
+ if (local_change != svn_wc_conflict_reason_missing)
+ {
+ err = verify_local_state_for_incoming_delete(conflict, option, ctx,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+ }
/* Get a copy of the conflict victim's properties. */
- err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, local_abspath,
+ err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, merge_source_abspath,
scratch_pool, scratch_pool);
if (err)
goto unlock_wc;
@@ -8506,7 +8665,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
* 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(local_abspath, moved_to_abspath, FALSE,
+ err = svn_io_copy_file(merge_source_abspath, moved_to_abspath, FALSE,
scratch_pool);
if (err)
goto unlock_wc;
@@ -8546,7 +8705,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool);
if (err)
goto unlock_wc;
- err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath,
+ err = svn_wc__move2(ctx->wc_ctx, merge_source_abspath, moved_to_abspath,
FALSE, /* ordinary (not meta-data only) move */
FALSE, /* mixed-revisions don't apply to files */
NULL, NULL, /* don't allow user to cancel here */
@@ -8604,19 +8763,27 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
operation == svn_wc_operation_switch)
{
/* Delete the tree conflict victim (clears the tree conflict marker). */
- err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
+ err = svn_wc_delete4(ctx->wc_ctx, victim_abspath, FALSE, FALSE,
NULL, NULL, /* don't allow user to cancel here */
NULL, NULL, /* no extra notification */
scratch_pool);
if (err)
goto unlock_wc;
}
+ else if (local_change == svn_wc_conflict_reason_missing)
+ {
+ /* Clear tree conflict marker. */
+ 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(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);
}
@@ -8637,6 +8804,521 @@ unlock_wc:
return SVN_NO_ERROR;
}
+/* 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. */
+static svn_error_t *
+resolve_both_moved_file_text_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;
+ 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_local_missing_details *local_details;
+ apr_array_header_t *local_moves;
+
+ 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_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(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);
+ }
+
+ /* 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;
+
+ 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 moving the locally moved
+ * directory to the incoming move target location, and then merging changes. */
+static svn_error_t *
+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 *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 *incoming_moved_repos_relpath;
+ 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;
+
+ 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_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));
+
+ 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_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(victim_abspath,
+ local_moved_to_abspath,
+ scratch_pool),
+ 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;
+
+ /* 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;
+
+ 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
+ * incoming move's target location to the local move's target location,
+ * overriding the incoming move. */
+static svn_error_t *
+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;
+ 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_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;
+
+ 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));
+ 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));
+
+ 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_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(victim_abspath,
+ local_moved_to_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ /* 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);
+ if (err)
+ goto unlock_wc;
+
+ /* 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)
+ 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. */
static svn_error_t *
resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
@@ -8767,11 +9449,14 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
svn_opt_revision_t incoming_new_opt_rev;
/* Revert the incoming move target directory. */
- SVN_ERR(svn_wc_revert5(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity,
- FALSE, NULL, TRUE, FALSE,
- NULL, NULL, /* no cancellation */
- ctx->notify_func2, ctx->notify_baton2,
- scratch_pool));
+ 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. */
@@ -8866,7 +9551,9 @@ unlock_wc:
return SVN_NO_ERROR;
}
-/* Implements conflict_option_resolve_func_t. */
+/* 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 *
resolve_local_move_file_merge(svn_client_conflict_option_t *option,
svn_client_conflict_t *conflict,
@@ -8894,6 +9581,12 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
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));
details = conflict->tree_conflict_local_details;
@@ -8909,8 +9602,31 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
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,
- details->moved_to_abspath,
+ merge_target_abspath,
scratch_pool, scratch_pool));
/* Fetch the common ancestor file's content. */
@@ -8955,7 +9671,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
&lock_abspath, ctx->wc_ctx,
svn_dirent_get_longest_ancestor(conflict->local_abspath,
- details->moved_to_abspath,
+ merge_target_abspath,
scratch_pool),
scratch_pool, scratch_pool));
@@ -8963,7 +9679,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
ctx->wc_ctx,
ancestor_tmp_abspath, incoming_tmp_abspath,
- details->moved_to_abspath,
+ merge_target_abspath,
NULL, NULL, NULL, /* labels */
NULL, NULL, /* conflict versions */
FALSE, /* dry run */
@@ -8973,7 +9689,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
NULL, NULL, /* conflict func/baton */
NULL, NULL, /* don't allow user to cancel here */
scratch_pool);
- svn_io_sleep_for_timestamps(details->moved_to_abspath, 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,
@@ -8994,7 +9710,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
svn_wc_notify_t *notify;
/* Tell the world about the file merge that just happened. */
- notify = svn_wc_create_notify(details->moved_to_abspath,
+ notify = svn_wc_create_notify(merge_target_abspath,
svn_wc_notify_update_update,
scratch_pool);
if (merge_content_outcome == svn_wc_merge_conflict)
@@ -9017,6 +9733,127 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *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)
{
@@ -9153,13 +9990,13 @@ svn_client_conflict_text_get_resolution_options(apr_array_header_t **options,
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,
@@ -9771,7 +10608,7 @@ configure_option_incoming_delete_ignore(svn_client_conflict_t *conflict,
is_local_move = (local_details != NULL &&
local_details->moves != NULL);
- if (!is_incoming_move && !is_local_move)
+ if (is_incoming_move || is_local_move)
return SVN_NO_ERROR;
}
@@ -9817,7 +10654,8 @@ configure_option_incoming_delete_accept(svn_client_conflict_t *conflict,
incoming_details->moves != NULL);
if (is_incoming_move &&
(local_change == svn_wc_conflict_reason_edited ||
- local_change == svn_wc_conflict_reason_moved_away))
+ local_change == svn_wc_conflict_reason_moved_away ||
+ local_change == svn_wc_conflict_reason_missing))
{
/* An option which accepts the incoming deletion makes no sense
* if we know there was a local move and/or an incoming move. */
@@ -9854,39 +10692,75 @@ describe_incoming_move_merge_conflict_option(
const char **description,
svn_client_conflict_t *conflict,
svn_client_ctx_t *ctx,
- struct conflict_tree_incoming_delete_details *details,
+ const char *moved_to_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- apr_array_header_t *move_target_wc_abspaths;
svn_wc_operation_t operation;
const char *victim_abspath;
- const char *moved_to_abspath;
+ svn_node_kind_t victim_node_kind;
const char *wcroot_abspath;
- 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 *);
-
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)
- *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));
+ {
+ 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(
@@ -9918,6 +10792,7 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
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);
@@ -9938,6 +10813,8 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
{
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)
@@ -9946,9 +10823,15 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
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,
- details,
+ moved_to_abspath,
scratch_pool,
scratch_pool));
add_resolution_option(
@@ -9999,6 +10882,8 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
{
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)
@@ -10007,9 +10892,15 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
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,
- details,
+ moved_to_abspath,
scratch_pool,
scratch_pool));
add_resolution_option(options, conflict,
@@ -10024,23 +10915,32 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
/* Configure 'local move file merge' resolution option for
* a tree conflict. */
static svn_error_t *
-configure_option_local_move_file_merge(svn_client_conflict_t *conflict,
- svn_client_ctx_t *ctx,
- apr_array_header_t *options,
- apr_pool_t *scratch_pool)
+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,
- NULL, conflict, scratch_pool,
+ &incoming_new_kind, conflict, scratch_pool,
scratch_pool));
if (operation == svn_wc_operation_merge &&
@@ -10048,62 +10948,37 @@ configure_option_local_move_file_merge(svn_client_conflict_t *conflict,
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)
+ 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;
+ apr_array_header_t *moves;
+ const char *moved_to_abspath;
+ const char *description;
- iterpool = svn_pool_create(scratch_pool);
- for (i = 0; i < details->moves->nelts; i++)
- {
- struct repos_move_info *move;
+ 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 *);
- 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);
+ 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 (apr_hash_count(wc_move_targets) > 0)
+ 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))
{
- apr_array_header_t *move_target_repos_relpaths;
- const svn_sort__item_t *item;
- apr_array_header_t *moved_to_abspaths;
- const char *description;
- const char *wcroot_abspath;
-
- /* 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 *));
-
- SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
- conflict->local_abspath,
- scratch_pool, scratch_pool));
- 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));
-
add_resolution_option(
options, conflict,
svn_client_conflict_option_local_move_file_text_merge,
@@ -10111,50 +10986,943 @@ configure_option_local_move_file_merge(svn_client_conflict_t *conflict,
description, resolve_local_move_file_merge);
}
else
- details->moved_to_abspath = NULL;
+ {
+ 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;
}
-svn_error_t *
-svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
- apr_array_header_t **possible_moved_to_repos_relpaths,
+/* 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,
- apr_pool_t *result_pool,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
- svn_client_conflict_t *conflict = option->conflict;
- struct conflict_tree_incoming_delete_details *details;
+ svn_client_conflict_option_id_t option_id;
const char *victim_abspath;
- apr_array_header_t *sorted_repos_relpaths;
- int i;
+ 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;
- 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);
+ 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
+ * local move's target location to the incoming move's target location,
+ * and reverting the local move. */
+static svn_error_t *
+resolve_both_moved_file_update_keep_incoming_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);
- details = conflict->tree_conflict_incoming_details;
- if (details == NULL || details->wc_move_targets == NULL)
+ 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,
- _("Getting a list of possible move targets "
+ _("The specified conflict resolution option "
"requires details for tree conflict at '%s' "
- "to be fetched from the repository first"),
+ "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_move_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,
+ local_moved_to_abspath, incoming_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);
+ }
+
+ /* Revert the copy-half of the local move. The delete-half of this move
+ * has already been deleted during the update/switch operation. */
+ err = svn_wc_revert6(ctx->wc_ctx, local_moved_to_abspath, svn_depth_empty,
+ 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;
+
+ 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 tree_conflict_get_details_func_t. */
+static svn_error_t *
+conflict_tree_get_details_update_local_moved_away(
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ struct conflict_tree_update_local_moved_away_details *details;
+ const char *incoming_old_repos_relpath;
+ svn_node_kind_t incoming_old_kind;
+
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, NULL, &incoming_old_kind,
+ conflict, scratch_pool, scratch_pool));
+
+ details = apr_pcalloc(conflict->pool, sizeof(*details));
+
+ details->wc_move_targets = apr_array_make(conflict->pool, 1,
+ sizeof(const char *));
+
+ /* Search the WC for copies of the conflict victim. */
+ SVN_ERR(svn_wc__find_copies_of_repos_path(&details->wc_move_targets,
+ conflict->local_abspath,
+ incoming_old_repos_relpath,
+ incoming_old_kind,
+ ctx->wc_ctx,
+ conflict->pool,
+ scratch_pool));
+
+ conflict->tree_conflict_local_details = details;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_both_moved_file_paths(const char **incoming_moved_to_abspath,
+ const char **local_moved_to_abspath,
+ svn_client_conflict_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ struct conflict_tree_incoming_delete_details *incoming_details;
+ apr_array_header_t *incoming_move_target_wc_abspaths;
+ svn_wc_operation_t operation;
+
+ operation = svn_client_conflict_get_operation(conflict);
- /* Return a copy of the repos replath candidate list. */
- sorted_repos_relpaths = svn_sort__hash(details->wc_move_targets,
+ *incoming_moved_to_abspath = NULL;
+ *local_moved_to_abspath = NULL;
+
+ incoming_details = conflict->tree_conflict_incoming_details;
+ if (incoming_details == NULL || incoming_details->moves == NULL ||
+ apr_hash_count(incoming_details->wc_move_targets) == 0)
+ return SVN_NO_ERROR;
+
+ incoming_move_target_wc_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(incoming_move_target_wc_abspaths,
+ incoming_details->wc_move_target_idx, const char *);
+
+ if (operation == svn_wc_operation_merge)
+ {
+ struct conflict_tree_local_missing_details *local_details;
+ apr_array_header_t *local_moves;
+
+ local_details = conflict->tree_conflict_local_details;
+ if (local_details == NULL ||
+ apr_hash_count(local_details->wc_move_targets) == 0)
+ return SVN_NO_ERROR;
+
+ 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 *);
+ }
+ else
+ {
+ struct conflict_tree_update_local_moved_away_details *local_details;
+
+ local_details = conflict->tree_conflict_local_details;
+ if (local_details == NULL ||
+ local_details->wc_move_targets->nelts == 0)
+ return SVN_NO_ERROR;
+
+ *local_moved_to_abspath =
+ APR_ARRAY_IDX(local_details->wc_move_targets,
+ local_details->preferred_move_target_idx,
+ const char *);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+conflict_tree_get_description_update_both_moved_file_merge(
+ const char **description,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *incoming_moved_to_abspath;
+ const char *local_moved_to_abspath;
+ svn_wc_operation_t operation;
+ const char *wcroot_abspath;
+
+ *description = NULL;
+
+ SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath,
+ &local_moved_to_abspath,
+ conflict, scratch_pool));
+ if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath, scratch_pool,
+ scratch_pool));
+
+ operation = svn_client_conflict_get_operation(conflict);
+
+ if (operation == svn_wc_operation_merge)
+ {
+ /* In case of a merge, the incoming move has A+ (copied) status... */
+ *description =
+ apr_psprintf(
+ scratch_pool,
+ _("apply changes to '%s' and revert addition of '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
+ scratch_pool),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
+ scratch_pool));
+ }
+ else
+ {
+ /* ...but in case of update/switch the local move has "A+" status. */
+ *description =
+ apr_psprintf(
+ scratch_pool,
+ _("override incoming move and merge incoming changes from '%s' "
+ "to '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
+ scratch_pool),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+conflict_tree_get_description_update_both_moved_file_move_merge(
+ const char **description,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *incoming_moved_to_abspath;
+ const char *local_moved_to_abspath;
+ svn_wc_operation_t operation;
+ const char *wcroot_abspath;
+
+ *description = NULL;
+
+ SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath,
+ &local_moved_to_abspath,
+ conflict, scratch_pool));
+ if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath, scratch_pool,
+ scratch_pool));
+
+ operation = svn_client_conflict_get_operation(conflict);
+
+ if (operation == svn_wc_operation_merge)
+ {
+ SVN_ERR(describe_incoming_move_merge_conflict_option(
+ description, conflict, ctx, local_moved_to_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ *description =
+ apr_psprintf(
+ scratch_pool,
+ _("accept incoming move and merge local changes from "
+ "'%s' to '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
+ scratch_pool),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Configure 'both moved file merge' resolution options for a tree conflict. */
+static svn_error_t *
+configure_option_both_moved_file_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_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;
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath, scratch_pool,
+ scratch_pool));
+
+ operation = svn_client_conflict_get_operation(conflict);
+ victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(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));
+
+ /* ### what about the switch operation? */
+ if (((operation == svn_wc_operation_merge &&
+ victim_node_kind == svn_node_none) ||
+ (operation == svn_wc_operation_update &&
+ victim_node_kind == svn_node_file)) &&
+ incoming_old_kind == svn_node_file &&
+ incoming_new_kind == svn_node_none &&
+ ((operation == svn_wc_operation_merge &&
+ local_change == svn_wc_conflict_reason_missing) ||
+ (operation == svn_wc_operation_update &&
+ local_change == svn_wc_conflict_reason_moved_away)) &&
+ incoming_change == svn_wc_conflict_action_delete)
+ {
+ const char *description;
+
+ SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge(
+ &description, conflict, ctx, conflict->pool, scratch_pool));
+
+ if (description == NULL) /* details not fetched yet */
+ return SVN_NO_ERROR;
+
+ add_resolution_option(
+ options, conflict, svn_client_conflict_option_both_moved_file_merge,
+ _("Merge to corresponding local location"),
+ description,
+ operation == svn_wc_operation_merge ?
+ resolve_both_moved_file_text_merge :
+ resolve_both_moved_file_update_keep_local_move);
+
+ SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge(
+ &description, conflict, ctx, conflict->pool, scratch_pool));
+
+ add_resolution_option(options, conflict,
+ svn_client_conflict_option_both_moved_file_move_merge,
+ _("Move and merge"), description,
+ operation == svn_wc_operation_merge ?
+ resolve_incoming_move_file_text_merge :
+ resolve_both_moved_file_update_keep_incoming_move);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Configure 'both moved dir merge' resolution options for a tree conflict. */
+static svn_error_t *
+configure_option_both_moved_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_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;
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath, scratch_pool,
+ scratch_pool));
+
+ operation = svn_client_conflict_get_operation(conflict);
+ victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(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 &&
+ victim_node_kind == svn_node_none &&
+ incoming_old_kind == svn_node_dir &&
+ incoming_new_kind == svn_node_none &&
+ local_change == svn_wc_conflict_reason_missing &&
+ incoming_change == svn_wc_conflict_action_delete)
+ {
+ struct conflict_tree_incoming_delete_details *incoming_details;
+ struct conflict_tree_local_missing_details *local_details;
+ const char *description;
+ apr_array_header_t *local_moves;
+ const char *local_moved_to_abspath;
+ const char *incoming_moved_to_abspath;
+ apr_array_header_t *incoming_move_target_wc_abspaths;
+
+ incoming_details = conflict->tree_conflict_incoming_details;
+ if (incoming_details == NULL || incoming_details->moves == NULL ||
+ apr_hash_count(incoming_details->wc_move_targets) == 0)
+ return SVN_NO_ERROR;
+
+ local_details = conflict->tree_conflict_local_details;
+ if (local_details == NULL ||
+ apr_hash_count(local_details->wc_move_targets) == 0)
+ return SVN_NO_ERROR;
+
+ 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 *);
+
+ incoming_move_target_wc_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(incoming_move_target_wc_abspaths,
+ incoming_details->wc_move_target_idx, const char *);
+
+ description =
+ apr_psprintf(
+ scratch_pool, _("apply changes to '%s' and revert addition of '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
+ scratch_pool),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
+ scratch_pool));
+ add_resolution_option(
+ options, conflict, svn_client_conflict_option_both_moved_dir_merge,
+ _("Merge to corresponding local location"),
+ description, resolve_both_moved_dir_merge);
+
+ SVN_ERR(describe_incoming_move_merge_conflict_option(
+ &description, conflict, ctx, local_moved_to_abspath,
+ scratch_pool, scratch_pool));
+ add_resolution_option(options, conflict,
+ svn_client_conflict_option_both_moved_dir_move_merge,
+ _("Move and merge"), description,
+ resolve_both_moved_dir_move_merge);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* 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,
+ apr_hash_t *wc_move_targets,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *sorted_repos_relpaths;
+ int i;
+
+ 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;
@@ -10170,37 +11938,115 @@ svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
}
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 &&
+ id != svn_client_conflict_option_both_moved_file_merge &&
+ id != svn_client_conflict_option_both_moved_file_move_merge &&
+ id != svn_client_conflict_option_both_moved_dir_merge &&
+ id != svn_client_conflict_option_both_moved_dir_move_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 && details->wc_siblings == 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));
+
+ if (details->wc_move_targets)
+ SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths,
+ details->wc_move_targets,
+ result_pool, scratch_pool));
+ else
+ *possible_moved_to_repos_relpaths = NULL;
+ }
+ 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'"),
@@ -10209,15 +12055,14 @@ svn_client_conflict_option_set_moved_to_repos_relpath(
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))
{
@@ -10225,16 +12070,7 @@ svn_client_conflict_option_set_moved_to_repos_relpath(
if (strcmp(move_target_repos_relpath, repos_relpath) == 0)
{
- details->move_target_repos_relpath = repos_relpath;
- 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));
-
+ *new_hash_key = repos_relpath;
return SVN_NO_ERROR;
}
}
@@ -10248,107 +12084,501 @@ svn_client_conflict_option_set_moved_to_repos_relpath(
}
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 &&
+ id != svn_client_conflict_option_both_moved_file_merge &&
+ id != svn_client_conflict_option_both_moved_file_move_merge &&
+ id != svn_client_conflict_option_both_moved_dir_merge &&
+ id != svn_client_conflict_option_both_moved_dir_move_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;
+ apr_array_header_t *move_target_wc_abspaths;
+ const char *moved_to_abspath;
+
+ 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. */
+ 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(
+ &option->description,
+ conflict, ctx,
+ moved_to_abspath,
+ 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,
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 *move_target_wc_abspaths;
+ svn_wc_operation_t operation;
+ 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_incoming_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 &&
+ id != svn_client_conflict_option_both_moved_file_merge &&
+ id != svn_client_conflict_option_both_moved_file_move_merge &&
+ id != svn_client_conflict_option_both_moved_dir_merge &&
+ id != svn_client_conflict_option_both_moved_dir_move_merge)
+ {
+ /* We cannot operate on this option. */
+ *possible_moved_to_abspaths = NULL;
+ return NULL;
+ }
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));
+ 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);
- move_target_wc_abspaths =
- svn_hash_gets(details->wc_move_targets,
- get_moved_to_repos_relpath(details, 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;
- /* Return a copy of the option's move target candidate list. */
- *possible_moved_to_abspaths =
- apr_array_make(result_pool, move_target_wc_abspaths->nelts,
- sizeof (const char *));
- for (i = 0; i < move_target_wc_abspaths->nelts; i++)
+ details = conflict->tree_conflict_local_details;
+ if (details == 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' "
+ "to be fetched from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ *possible_moved_to_abspaths = apr_array_make(result_pool, 1,
+ sizeof (const char *));
+ 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. */
+ if (details->wc_siblings)
+ {
+ for (i = 0; i < details->wc_siblings->nelts; i++)
+ {
+ const char *sibling_abspath;
+
+ sibling_abspath = APR_ARRAY_IDX(details->wc_siblings, i,
+ const char *);
+ APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
+ apr_pstrdup(result_pool, sibling_abspath);
+ }
+ }
+ }
+ else if ((operation == svn_wc_operation_update ||
+ operation == svn_wc_operation_switch) &&
+ incoming_change == svn_wc_conflict_action_delete &&
+ local_change == svn_wc_conflict_reason_moved_away)
{
- const char *moved_to_abspath;
+ struct conflict_tree_update_local_moved_away_details *details;
- 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);
+ 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));
+
+ /* Return a copy of the option's move target candidate list. */
+ *possible_moved_to_abspaths =
+ apr_array_make(result_pool, details->wc_move_targets->nelts,
+ sizeof (const char *));
+ for (i = 0; i < details->wc_move_targets->nelts; i++)
+ {
+ const char *moved_to_abspath;
+
+ moved_to_abspath = APR_ARRAY_IDX(details->wc_move_targets, i,
+ const char *);
+ APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
+ apr_pstrdup(result_pool, moved_to_abspath);
+ }
+ }
+ else
+ {
+ struct conflict_tree_incoming_delete_details *details;
+ apr_array_header_t *move_target_wc_abspaths;
+
+ 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));
+
+ move_target_wc_abspaths =
+ svn_hash_gets(details->wc_move_targets,
+ get_moved_to_repos_relpath(details, scratch_pool));
+
+ /* Return a copy of the option's move target candidate list. */
+ *possible_moved_to_abspaths =
+ apr_array_make(result_pool, move_target_wc_abspaths->nelts,
+ sizeof (const char *));
+ 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);
+ }
}
return SVN_NO_ERROR;
}
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,
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 *move_target_wc_abspaths;
+ 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_incoming_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 &&
+ id != svn_client_conflict_option_both_moved_file_merge &&
+ id != svn_client_conflict_option_both_moved_file_move_merge &&
+ id != svn_client_conflict_option_both_moved_dir_merge &&
+ id != svn_client_conflict_option_both_moved_dir_move_merge)
+ return NULL; /* We cannot operate on this option. Nothing to do. */
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));
+ 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);
- move_target_wc_abspaths =
- svn_hash_gets(details->wc_move_targets,
- get_moved_to_repos_relpath(details, 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;
+ const char *preferred_sibling;
- 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));
+ 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 &&
+ 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));
- /* Record the user's preference. */
- details->wc_move_target_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. */
- SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description,
- conflict, ctx,
- details,
- conflict->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 if ((operation == svn_wc_operation_update ||
+ operation == svn_wc_operation_switch) &&
+ incoming_change == svn_wc_conflict_action_delete &&
+ local_change == svn_wc_conflict_reason_moved_away)
+ {
+ struct conflict_tree_update_local_moved_away_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));
+
+ if (preferred_move_target_idx < 0 ||
+ preferred_move_target_idx > details->wc_move_targets->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->preferred_move_target_idx = preferred_move_target_idx;
+
+ /* Update option description. */
+ if (id == svn_client_conflict_option_both_moved_file_merge)
+ SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge(
+ &option->description, conflict, ctx, conflict->pool,
+ scratch_pool));
+ else if (id == svn_client_conflict_option_both_moved_file_move_merge)
+ SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge(
+ &option->description, conflict, ctx, conflict->pool, scratch_pool));
+#if 0 /* ### TODO: Also handle options for directories! */
+ else if (id == svn_client_conflict_option_both_moved_dir_merge)
+ {
+ }
+ else if (id == svn_client_conflict_option_both_moved_dir_move_merge)
+ {
+ }
+#endif
+ else
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Unexpected option id '%d'"), id);
+ }
+ else
+ {
+ struct conflict_tree_incoming_delete_details *details;
+ apr_array_header_t *move_target_wc_abspaths;
+ const char *moved_to_abspath;
+
+ 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));
+
+ move_target_wc_abspaths =
+ svn_hash_gets(details->wc_move_targets,
+ get_moved_to_repos_relpath(details, scratch_pool));
+
+ 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. */
+ 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(&option->description,
+ conflict, ctx,
+ moved_to_abspath,
+ conflict->pool,
+ scratch_pool));
+ }
return SVN_NO_ERROR;
}
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,
@@ -10401,8 +12631,15 @@ svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options,
scratch_pool));
SVN_ERR(configure_option_incoming_dir_merge(conflict, ctx, *options,
scratch_pool));
- SVN_ERR(configure_option_local_move_file_merge(conflict, ctx, *options,
+ SVN_ERR(configure_option_local_move_file_or_dir_merge(conflict, ctx,
+ *options,
+ scratch_pool));
+ SVN_ERR(configure_option_sibling_move_merge(conflict, ctx, *options,
+ scratch_pool));
+ SVN_ERR(configure_option_both_moved_file_merge(conflict, ctx, *options,
scratch_pool));
+ SVN_ERR(configure_option_both_moved_dir_merge(conflict, ctx, *options,
+ scratch_pool));
return SVN_NO_ERROR;
}
@@ -10947,6 +13184,7 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict,
apr_pool_t *scratch_pool)
{
svn_boolean_t tree_conflicted;
+ svn_wc_operation_t operation;
svn_wc_conflict_action_t incoming_change;
svn_wc_conflict_reason_t local_change;
@@ -10963,6 +13201,7 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict,
conflict->tree_conflict_get_local_description_func =
conflict_tree_get_local_description_generic;
+ 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);
@@ -10997,6 +13236,12 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict,
conflict->tree_conflict_get_local_details_func =
conflict_tree_get_details_local_missing;
}
+ else if (local_change == svn_wc_conflict_reason_moved_away &&
+ operation == svn_wc_operation_update /* ### what about switch? */)
+ {
+ conflict->tree_conflict_get_local_details_func =
+ conflict_tree_get_details_update_local_moved_away;
+ }
return SVN_NO_ERROR;
}