You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2016/06/05 15:03:54 UTC
svn commit: r1746927 [3/8] - in /subversion/branches/authzperf: ./ build/
contrib/client-side/ contrib/client-side/svnmerge/ contrib/hook-scripts/
contrib/server-side/ contrib/server-side/fsfsfixer/fixer/
notes/directory-index/ notes/move-tracking/ sub...
Modified: subversion/branches/authzperf/subversion/libsvn_client/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_client/conflicts.c?rev=1746927&r1=1746926&r2=1746927&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_client/conflicts.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_client/conflicts.c Sun Jun 5 15:03:52 2016
@@ -234,26 +234,278 @@ static const svn_token_map_t map_conflic
{ NULL, 0 }
};
+/* Describes a server-side move (really a copy+delete within the same
+ * revision) which was identified by scanning the revision log. */
+struct repos_move_info {
+ /* The repository relpath the node was moved from. */
+ const char *moved_from_repos_relpath;
+
+ /* The repository relpath the node was moved to. */
+ const char *moved_to_repos_relpath;
+
+ /* The revision in which this move was committed. */
+ svn_revnum_t rev;
+
+ /* The author who commited the revision in which this move was committed. */
+ const char *rev_author;
+
+ /* The copyfrom revision of the moved-to path. */
+ svn_revnum_t copyfrom_rev;
+
+ /* Prev and next pointers. NULL if no prior or next move exists. */
+ struct repos_move_info *prev;
+ struct repos_move_info *next;
+};
+
+/* Set *RELATED to true if the deleted node at repository relpath
+ * DELETED_REPOS_RELPATH@DELETED_REV is ancestrally related to the node at
+ * repository relpath COPYFROM_PATH@COPYFROM_REV, else set it to false. */
+static svn_error_t *
+check_move_ancestry(svn_boolean_t *related,
+ const char *repos_root_url,
+ const char *deleted_repos_relpath,
+ svn_revnum_t deleted_rev,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *locations;
+ const char *deleted_url;
+ const char *deleted_location;
+ svn_ra_session_t *ra_session;
+ const char *corrected_url;
+ apr_array_header_t *location_revisions;
+
+ *related = FALSE;
+
+ location_revisions = apr_array_make(scratch_pool, 1, sizeof(svn_revnum_t));
+ APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = copyfrom_rev;
+ deleted_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool,
+ repos_root_url, "/",
+ deleted_repos_relpath,
+ NULL),
+ scratch_pool);
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+ deleted_url, NULL,
+ NULL, FALSE, FALSE,
+ ctx, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_ra_get_locations(ra_session, &locations, "",
+ deleted_rev - 1, location_revisions,
+ scratch_pool));
+
+ deleted_location = apr_hash_get(locations, ©from_rev,
+ sizeof(svn_revnum_t));
+ if (deleted_location)
+ {
+ if (deleted_location[0] == '/')
+ deleted_location++;
+ *related = (strcmp(deleted_location, copyfrom_path) == 0);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct copy_info {
+ const char *copyto_path;
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+};
+
+/* Update MOVES_TABLE and MOVED_PATHS based on information from
+ * revision data in LOG_ENTRY, COPIES, and DELETED_PATHS. */
+static svn_error_t *
+find_moves_in_revision(apr_hash_t *moves_table,
+ apr_hash_t *moved_paths,
+ svn_log_entry_t *log_entry,
+ apr_hash_t *copies,
+ apr_array_header_t *deleted_paths,
+ const char *repos_root_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ apr_array_header_t *copies_with_same_source_path;
+ svn_boolean_t related;
+ int i, j;
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < deleted_paths->nelts; i++)
+ {
+ const char *deleted_repos_relpath;
+
+ deleted_repos_relpath = APR_ARRAY_IDX(deleted_paths, i, const char *);
+
+ /* See if we can match any copies to this deleted path. */
+ copies_with_same_source_path = apr_hash_get(copies,
+ deleted_repos_relpath,
+ APR_HASH_KEY_STRING);
+ if (copies_with_same_source_path == NULL)
+ continue;
+
+ for (j = 0; j < copies_with_same_source_path->nelts; j++)
+ {
+ struct copy_info *copy;
+ struct repos_move_info *move;
+ struct repos_move_info *next_move;
+ svn_string_t *author;
+ apr_array_header_t *moves;
+
+ svn_pool_clear(iterpool);
+
+ copy = APR_ARRAY_IDX(copies_with_same_source_path, j,
+ struct copy_info *);
+
+ /* We found a deleted node which matches the copyfrom path of
+ * a copied node. Verify that the deleted node is an ancestor
+ * of the copied node. When tracing back history of the deleted node
+ * from revision log_entry->revision-1 (where the deleted node is
+ * guaranteed to exist) to the copyfrom-revision, we must end up
+ * at the copyfrom-path. */
+ SVN_ERR(check_move_ancestry(&related, repos_root_url,
+ deleted_repos_relpath,
+ log_entry->revision,
+ copy->copyfrom_path,
+ copy->copyfrom_rev,
+ ctx, iterpool));
+ if (!related)
+ continue;
+
+ /* ### TODO:
+ * If the node was not copied from the most recent last-changed
+ * revision of the deleted node, this is not a move but a
+ * "copy from the past + delete". */
+
+ /* Remember details of this move. */
+ move = apr_pcalloc(result_pool, sizeof(*move));
+ move->moved_from_repos_relpath = apr_pstrdup(result_pool,
+ deleted_repos_relpath);
+ move->moved_to_repos_relpath = apr_pstrdup(result_pool,
+ copy->copyto_path);
+ move->rev = log_entry->revision;
+ author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
+ move->rev_author = apr_pstrdup(result_pool, author->data);
+ move->copyfrom_rev = copy->copyfrom_rev;
+
+ /* Link together multiple moves of the same node.
+ * Note that we're traversing history backwards, so moves already
+ * present in the list happened in younger revisions. */
+ next_move = svn_hash_gets(moved_paths, move->moved_to_repos_relpath);
+ if (next_move)
+ {
+ /* Tracing back history of the delete-half of the next move
+ * to the copyfrom-revision of the prior move we must end up
+ * at the delete-half of the prior move. */
+ SVN_ERR(check_move_ancestry(&related, repos_root_url,
+ next_move->moved_from_repos_relpath,
+ next_move->rev,
+ move->moved_from_repos_relpath,
+ move->copyfrom_rev,
+ ctx, iterpool));
+ if (related)
+ {
+ SVN_ERR_ASSERT(move->rev < next_move->rev);
+
+ /* Prepend this move to the linked list. */
+ move->next = next_move;
+ next_move->prev = move;
+ }
+ }
+
+ /* Make this move the head of our next-move linking map. */
+ svn_hash_sets(moved_paths, move->moved_from_repos_relpath, move);
+
+ /* Add this move to the list of moves in this revision. */
+ moves = apr_hash_get(moves_table, &move->rev, sizeof(svn_revnum_t));
+ if (moves == NULL)
+ {
+ /* This is the first move in this revision. Create the list. */
+ moves = apr_array_make(result_pool, 1,
+ sizeof(struct repos_move_info *));
+ apr_hash_set(moves_table, &move->rev, sizeof(svn_revnum_t),
+ moves);
+ }
+ APR_ARRAY_PUSH(moves, struct repos_move_info *) = move;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
struct find_deleted_rev_baton
{
+ /* Variables below are arguments provided by the caller of
+ * svn_ra_get_log2(). */
const char *deleted_repos_relpath;
const char *related_repos_relpath;
svn_revnum_t related_repos_peg_rev;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ svn_client_ctx_t *ctx;
+ /* Variables below are results for the caller of svn_ra_get_log2(). */
svn_revnum_t deleted_rev;
const char *deleted_rev_author;
svn_node_kind_t replacing_node_kind;
-
- const char *repos_root_url;
- const char *repos_uuid;
- svn_client_ctx_t *ctx;
apr_pool_t *result_pool;
+
+ /* A hash table mapping a revision number to an array of struct
+ * repos_move_info * elements, describing moves.
+ *
+ * Must be allocated in RESULT_POOL by the caller of svn_ra_get_log2().
+ *
+ * If the node was moved, the DELETED_REV is present in this table,
+ * perhaps along with additional revisions.
+ *
+ * Given a sequence of moves which happened in the repository, such as:
+ * rA: mv x->z
+ * rA: mv a->b
+ * rB: mv b->c
+ * rC: mv c->d
+ * we map each revision number to all the moves which happened in the
+ * revision, which looks as follows:
+ * rA : [(x->z), (a->b)]
+ * rB : [(b->c)]
+ * rC : [(c->d)]
+ * This allows us to later find relevant moves based on a revision number.
+ *
+ * Additionally, we embed the number of the revision in which a move was
+ * found inside the repos_move_info structure:
+ * rA : [(rA, x->z), (rA, a->b)]
+ * rB : [(rB, b->c)]
+ * rC : [(rC, c->d)]
+ * And also, all moves pertaining to the same node are chained into a
+ * doubly-linked list via 'next' and 'prev' pointers (see definition of
+ * struct repos_move_info). This can be visualized as follows:
+ * rA : [(rA, x->z, prev=>NULL, next=>NULL),
+ * (rA, a->b, prev=>NULL, next=>(rB, b->c))]
+ * rB : [(rB, b->c), prev=>(rA, a->b), next=>(rC, c->d)]
+ * rC : [(rC, c->d), prev=>(rB, c->d), next=>NULL]
+ * This way, we can look up all moves relevant to a node, forwards and
+ * backwards in history, once we have located one move in the chain.
+ *
+ * In the above example, the data tells us that within the revision
+ * range rA:C, a was moved to d. However, within the revision range
+ * rA;B, a was moved to b.
+ */
+ apr_hash_t *moves_table;
+
+ /* Variables below hold state for find_deleted_rev() and are not
+ * intended to be used by the caller of svn_ra_get_log2().
+ * Like all other variables, they must be initialized, however. */
+
+ /* Temporary map of moved paths to struct repos_move_info.
+ * Used to link multiple moves of the same node across revisions. */
+ apr_hash_t *moved_paths;
};
/* Implements svn_log_entry_receiver_t.
*
- * Find the revision in which a node, ancestrally related to the node
- * specified via find_deleted_rev_baton, was deleted, When the revision
+ * Find the revision in which a node, optionally ancestrally related to the
+ * node specified via find_deleted_rev_baton, was deleted, When the revision
* was found, store it in BATON->DELETED_REV and abort the log operation
* by raising SVN_ERR_CANCELLED.
*
@@ -266,7 +518,11 @@ struct find_deleted_rev_baton
*
* This function answers the same question as svn_ra_get_deleted_rev() but
* works in cases where we do not already know a revision in which the deleted
- * node once used to exist. */
+ * node once used to exist.
+ *
+ * If the node node was moved, rather than deleted, return move information
+ * in BATON->MOVE.
+ */
static svn_error_t *
find_deleted_rev(void *baton,
svn_log_entry_t *log_entry,
@@ -275,57 +531,110 @@ find_deleted_rev(void *baton,
struct find_deleted_rev_baton *b = baton;
apr_hash_index_t *hi;
apr_pool_t *iterpool;
+ svn_boolean_t deleted_node_found = FALSE;
+ apr_array_header_t *deleted_paths;
+ apr_hash_t *copies;
/* No paths were changed in this revision. Nothing to do. */
if (! log_entry->changed_paths2)
return SVN_NO_ERROR;
+ copies = apr_hash_make(scratch_pool);
+ deleted_paths = apr_array_make(scratch_pool, 0, sizeof(const char *));
iterpool = svn_pool_create(scratch_pool);
for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
hi != NULL;
hi = apr_hash_next(hi))
{
- void *val;
- const char *path;
- svn_log_changed_path2_t *log_item;
+ const char *changed_path = apr_hash_this_key(hi);
+ svn_log_changed_path2_t *log_item = apr_hash_this_val(hi);
svn_pool_clear(iterpool);
+ /* ### Remove leading slash from paths in log entries. */
+ if (changed_path[0] == '/')
+ changed_path++;
- apr_hash_this(hi, (void *) &path, NULL, &val);
- log_item = val;
+ /* For move detection, scan for copied nodes in this revision. */
+ if (log_item->action == 'A' && log_item->copyfrom_path)
+ {
+ struct copy_info *copy;
+ apr_array_header_t *copies_with_same_source_path;
- /* ### Remove leading slash from paths in log entries. */
- if (path[0] == '/')
- path = svn_relpath_canonicalize(path, iterpool);
+ if (log_item->copyfrom_path[0] == '/')
+ log_item->copyfrom_path++;
+
+ copy = apr_palloc(scratch_pool, sizeof(*copy));
+ copy->copyto_path = changed_path;
+ copy->copyfrom_path = log_item->copyfrom_path;
+ copy->copyfrom_rev = log_item->copyfrom_rev;
+ copies_with_same_source_path = apr_hash_get(copies,
+ log_item->copyfrom_path,
+ APR_HASH_KEY_STRING);
+ if (copies_with_same_source_path == NULL)
+ {
+ copies_with_same_source_path = apr_array_make(
+ scratch_pool, 1,
+ sizeof(struct copy_info *));
+ apr_hash_set(copies, copy->copyfrom_path, APR_HASH_KEY_STRING,
+ copies_with_same_source_path);
+ }
+ APR_ARRAY_PUSH(copies_with_same_source_path,
+ struct copy_info *) = copy;
+ }
- if (svn_path_compare_paths(b->deleted_repos_relpath, path) == 0
- && (log_item->action == 'D' || log_item->action == 'R'))
+ /* For move detection, store all deleted_paths.
+ *
+ * ### This also stores deletions which happened inside copies.
+ * ### But we are not able to handle them at present.
+ * ### Consider: cp A B; mv B/foo C/foo
+ * ### Copyfrom for C/foo is now A/foo, even though C/foo was moved
+ * ### here from B/foo. We don't detect such moves at present since
+ * ### A/foo was not deleted. It is B/foo which was deleted.
+ */
+ if (log_item->action == 'D' || log_item->action == 'R')
+ APR_ARRAY_PUSH(deleted_paths, const char *) =
+ apr_pstrdup(scratch_pool, changed_path);
+
+ /* Check if we found the deleted node we're looking for. */
+ if (!deleted_node_found &&
+ svn_path_compare_paths(b->deleted_repos_relpath, changed_path) == 0 &&
+ (log_item->action == 'D' || log_item->action == 'R'))
{
- svn_client__pathrev_t *yca_loc;
- svn_client__pathrev_t *loc1;
- svn_client__pathrev_t *loc2;
-
- /* We found a deleted node which occupies the correct path.
- * To be certain that this is the deleted node we're looking for,
- * we must establish whether it is ancestrally related to the
- * "related node" specified in our baton. */
- loc1 = svn_client__pathrev_create_with_relpath(
- b->repos_root_url, b->repos_uuid, b->related_repos_peg_rev,
- b->related_repos_relpath, iterpool);
- loc2 = svn_client__pathrev_create_with_relpath(
- b->repos_root_url, b->repos_uuid, log_entry->revision - 1,
- b->deleted_repos_relpath, iterpool);
- SVN_ERR(svn_client__get_youngest_common_ancestor(&yca_loc, loc1, loc2,
- NULL, b->ctx,
- iterpool,
- iterpool));
- if (yca_loc != NULL)
+ deleted_node_found = TRUE;
+
+ if (b->related_repos_relpath != NULL &&
+ b->related_repos_peg_rev != SVN_INVALID_REVNUM)
+ {
+ svn_client__pathrev_t *yca_loc = NULL;
+ svn_client__pathrev_t *loc1;
+ svn_client__pathrev_t *loc2;
+
+ /* We found a deleted node which occupies the correct path.
+ * To be certain that this is the deleted node we're looking for,
+ * we must establish whether it is ancestrally related to the
+ * "related node" specified in our baton. */
+ loc1 = svn_client__pathrev_create_with_relpath(
+ b->repos_root_url, b->repos_uuid,
+ b->related_repos_peg_rev,
+ b->related_repos_relpath, iterpool);
+ loc2 = svn_client__pathrev_create_with_relpath(
+ b->repos_root_url, b->repos_uuid,
+ log_entry->revision - 1,
+ b->deleted_repos_relpath, iterpool);
+ SVN_ERR(svn_client__get_youngest_common_ancestor(&yca_loc,
+ loc1, loc2,
+ NULL, b->ctx,
+ iterpool,
+ iterpool));
+ deleted_node_found = (yca_loc != NULL);
+ }
+
+ if (deleted_node_found)
{
svn_string_t *author;
- /* Found the correct node, we are done. */
- b->deleted_rev = log_entry->revision;
+ b->deleted_rev = log_entry->revision;
author = svn_hash_gets(log_entry->revprops,
SVN_PROP_REVISION_AUTHOR);
b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data);
@@ -334,12 +643,22 @@ find_deleted_rev(void *baton,
b->replacing_node_kind = log_item->node_kind;
else
b->replacing_node_kind = svn_node_none;
- return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
}
}
}
svn_pool_destroy(iterpool);
+ /* Check for moves in this revision */
+ SVN_ERR(find_moves_in_revision(b->moves_table, b->moved_paths,
+ log_entry, copies, deleted_paths,
+ b->repos_root_url, b->ctx,
+ b->result_pool, scratch_pool));
+ if (deleted_node_found)
+ {
+ /* We're done. Abort the log operation. */
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
+ }
+
return SVN_NO_ERROR;
}
@@ -776,11 +1095,14 @@ describe_local_dir_node_change(const cha
* If the node was replaced rather than deleted, set *REPLACING_NODE_KIND to
* the node kind of the replacing node. Else, set it to svn_node_unknown.
* Only request the log for revisions up to END_REV from the server.
+ * If the deleted node was moved, provide move information in *MOVE.
+ * If the node was not moved,set *MOVE to NULL.
*/
static svn_error_t *
find_revision_for_suspected_deletion(svn_revnum_t *deleted_rev,
const char **deleted_rev_author,
svn_node_kind_t *replacing_node_kind,
+ struct repos_move_info **move,
svn_client_conflict_t *conflict,
const char *deleted_basename,
const char *parent_repos_relpath,
@@ -826,7 +1148,9 @@ find_revision_for_suspected_deletion(svn
b.repos_root_url = repos_root_url;
b.repos_uuid = repos_uuid;
b.ctx = conflict->ctx;
- b.result_pool = scratch_pool;
+ b.moves_table = apr_hash_make(result_pool);
+ b.moved_paths = apr_hash_make(scratch_pool);
+ b.result_pool = result_pool;
err = svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
0, /* no limit */
@@ -841,7 +1165,7 @@ find_revision_for_suspected_deletion(svn
if (err->apr_err == SVN_ERR_CANCELLED &&
b.deleted_rev != SVN_INVALID_REVNUM)
{
- /* Log operation was aborted because we found a YCA. */
+ /* Log operation was aborted because we found deleted rev. */
svn_error_clear(err);
}
else
@@ -855,12 +1179,42 @@ find_revision_for_suspected_deletion(svn
*deleted_rev = SVN_INVALID_REVNUM;
*deleted_rev_author = NULL;
*replacing_node_kind = svn_node_unknown;
+ *move = NULL;
return SVN_NO_ERROR;
}
+ else
+ {
+ apr_array_header_t *moves;
+
+ *deleted_rev = b.deleted_rev;
+ *deleted_rev_author = b.deleted_rev_author;
+ *replacing_node_kind = b.replacing_node_kind;
+
+ /* Look for a move which affects the deleted node. */
+ moves = apr_hash_get(b.moves_table, &b.deleted_rev,
+ sizeof(svn_revnum_t));
+ if (moves)
+ {
+ int i;
- *deleted_rev = b.deleted_rev;
- *deleted_rev_author = b.deleted_rev_author;
- *replacing_node_kind = b.replacing_node_kind;
+ for (i = 0; i < moves->nelts; i++)
+ {
+ struct repos_move_info *this_move;
+
+ this_move = APR_ARRAY_IDX(moves, i, struct repos_move_info *);
+ if (strcmp(b.deleted_repos_relpath,
+ this_move->moved_from_repos_relpath) == 0)
+ {
+ /* Because b->moves_table lives in result_pool
+ * there is no need to deep-copy here. */
+ *move = this_move;
+ break;
+ }
+ }
+ }
+ else
+ *move = NULL;
+ }
return SVN_NO_ERROR;
}
@@ -873,6 +1227,10 @@ struct conflict_tree_local_missing_detai
/* Author who committed DELETED_REV. */
const char *deleted_rev_author;
+
+ /* Move information. If not NULL, the first move happened in DELETED_REV.
+ * Follow MOVE->NEXT for subsequent moves in later revisions. */
+ struct repos_move_info *move;
};
/* Implements tree_conflict_get_details_func_t. */
@@ -890,6 +1248,7 @@ conflict_tree_get_details_local_missing(
svn_node_kind_t replacing_node_kind;
const char *deleted_basename;
struct conflict_tree_local_missing_details *details;
+ struct repos_move_info *move;
/* We only handle merges here. */
if (svn_client_conflict_get_operation(conflict) != svn_wc_operation_merge)
@@ -916,19 +1275,20 @@ conflict_tree_get_details_local_missing(
scratch_pool,
scratch_pool));
SVN_ERR(find_revision_for_suspected_deletion(
- &deleted_rev, &deleted_rev_author, &replacing_node_kind,
+ &deleted_rev, &deleted_rev_author, &replacing_node_kind, &move,
conflict, deleted_basename, parent_repos_relpath,
old_rev < new_rev ? new_rev : old_rev, 0,
old_rev < new_rev ? new_repos_relpath : old_repos_relpath,
old_rev < new_rev ? new_rev : old_rev,
- scratch_pool, scratch_pool));
+ conflict->pool, scratch_pool));
if (deleted_rev == SVN_INVALID_REVNUM)
return SVN_NO_ERROR;
details = apr_pcalloc(conflict->pool, sizeof(*details));
details->deleted_rev = deleted_rev;
- details->deleted_rev_author = apr_pstrdup(conflict->pool, deleted_rev_author);
+ details->deleted_rev_author = deleted_rev_author;
+ details->move = move;
conflict->tree_conflict_local_details = details;
@@ -968,10 +1328,13 @@ describe_local_none_node_change(const ch
*description = _("No such file or directory was found in the "
"working copy.");
else if (operation == svn_wc_operation_merge)
- *description = _("No such file or directory was found in the "
- "merge target working copy.\nThe item may "
- "have been deleted or moved away in the "
- "repository's history.");
+ {
+ /* ### display deleted revision */
+ *description = _("No such file or directory was found in the "
+ "merge target working copy.\nThe item may "
+ "have been deleted or moved away in the "
+ "repository's history.");
+ }
break;
case svn_wc_conflict_reason_unversioned:
*description = _("An unversioned item was found in the working "
@@ -996,6 +1359,29 @@ describe_local_none_node_change(const ch
return SVN_NO_ERROR;
}
+/* Append a description of all moves in the MOVE chain to DESCRIPTION. */
+static const char *
+append_moved_to_chain_description(const char *description,
+ struct repos_move_info *move,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (move == NULL)
+ return description;
+
+ while (move)
+ {
+ description = apr_psprintf(scratch_pool,
+ _("%s\nAnd then moved away to '^/%s' by "
+ "%s in r%ld."),
+ description, move->moved_to_repos_relpath,
+ move->rev_author, move->rev);
+ move = move->next;
+ }
+
+ return apr_pstrdup(result_pool, description);
+}
+
/* Implements tree_conflict_get_description_func_t. */
static svn_error_t *
conflict_tree_get_local_description_generic(const char **description,
@@ -1044,12 +1430,27 @@ conflict_tree_get_description_local_miss
return svn_error_trace(conflict_tree_get_local_description_generic(
description, conflict, result_pool, scratch_pool));
- *description = apr_psprintf(
- result_pool,
- _("No such file or directory was found in the "
- "merge target working copy.\nThe item was "
- "deleted or moved away in r%ld by %s."),
- details->deleted_rev, details->deleted_rev_author);
+ if (details->move)
+ {
+ *description = apr_psprintf(
+ result_pool,
+ _("No such file or directory was found in the "
+ "merge target working copy.\nThe item was "
+ "moved away to '^/%s' in r%ld by %s."),
+ details->move->moved_to_repos_relpath,
+ details->move->rev, details->move->rev_author);
+ *description = append_moved_to_chain_description(*description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ *description = apr_psprintf(
+ result_pool,
+ _("No such file or directory was found in the "
+ "merge target working copy.\nThe item was "
+ "deleted in r%ld by %s."),
+ details->deleted_rev, details->deleted_rev_author);
return SVN_NO_ERROR;
}
@@ -1365,6 +1766,11 @@ struct conflict_tree_incoming_delete_det
/* New node kind for a replaced node. This is svn_node_none for deletions. */
svn_node_kind_t replacing_node_kind;
+
+ /* Move information. If not NULL, the first move happened in DELETED_REV
+ * or in ADDED_REV (in which case moves should be interpreted in reverse).
+ * Follow MOVE->NEXT for subsequent moves in later revisions. */
+ struct repos_move_info *move;
};
static const char *
@@ -1373,7 +1779,8 @@ describe_incoming_deletion_upon_update(
svn_node_kind_t victim_node_kind,
svn_revnum_t old_rev,
svn_revnum_t new_rev,
- apr_pool_t *result_pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
if (details->replacing_node_kind == svn_node_file ||
details->replacing_node_kind == svn_node_symlink)
@@ -1423,22 +1830,71 @@ describe_incoming_deletion_upon_update(
else
{
if (victim_node_kind == svn_node_dir)
- return apr_psprintf(result_pool,
- _("Directory updated from r%ld to r%ld was "
- "deleted or moved by %s in r%ld."),
- old_rev, new_rev,
- details->rev_author, details->deleted_rev);
+ {
+ if (details->move)
+ {
+ const char *description =
+ apr_psprintf(result_pool,
+ _("Directory updated from r%ld to r%ld was "
+ "moved to '^/%s' by %s in r%ld."),
+ old_rev, new_rev,
+ details->move->moved_to_repos_relpath,
+ details->rev_author, details->deleted_rev);
+ return append_moved_to_chain_description(description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ return apr_psprintf(result_pool,
+ _("Directory updated from r%ld to r%ld was "
+ "deleted by %s in r%ld."),
+ old_rev, new_rev,
+ details->rev_author, details->deleted_rev);
+ }
else if (victim_node_kind == svn_node_file ||
victim_node_kind == svn_node_symlink)
- return apr_psprintf(result_pool,
- _("File updated from r%ld to r%ld was deleted or "
- "moved by %s in r%ld."), old_rev, new_rev,
- details->rev_author, details->deleted_rev);
+ {
+ if (details->move)
+ {
+ const char *description =
+ apr_psprintf(result_pool,
+ _("File updated from r%ld to r%ld was moved "
+ "to '^/%s' by %s in r%ld."), old_rev, new_rev,
+ details->move->moved_to_repos_relpath,
+ details->rev_author, details->deleted_rev);
+ return append_moved_to_chain_description(description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ return apr_psprintf(result_pool,
+ _("File updated from r%ld to r%ld was "
+ "deleted by %s in r%ld."), old_rev, new_rev,
+ details->rev_author, details->deleted_rev);
+ }
else
- return apr_psprintf(result_pool,
- _("Item updated from r%ld to r%ld was deleted or "
- "moved by %s in r%ld."), old_rev, new_rev,
- details->rev_author, details->deleted_rev);
+ {
+ if (details->move)
+ {
+ const char *description =
+ apr_psprintf(result_pool,
+ _("Item updated from r%ld to r%ld was moved "
+ "to '^/%s' by %s in r%ld."), old_rev, new_rev,
+ details->move->moved_to_repos_relpath,
+ details->rev_author, details->deleted_rev);
+ return append_moved_to_chain_description(description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ return apr_psprintf(result_pool,
+ _("Item updated from r%ld to r%ld was "
+ "deleted by %s in r%ld."), old_rev, new_rev,
+ details->rev_author, details->deleted_rev);
+ }
}
}
@@ -1529,7 +1985,8 @@ describe_incoming_deletion_upon_switch(
svn_revnum_t old_rev,
const char *new_repos_relpath,
svn_revnum_t new_rev,
- apr_pool_t *result_pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
if (details->replacing_node_kind == svn_node_file ||
details->replacing_node_kind == svn_node_symlink)
@@ -1593,30 +2050,87 @@ describe_incoming_deletion_upon_switch(
else
{
if (victim_node_kind == svn_node_dir)
- return apr_psprintf(result_pool,
- _("Directory switched from\n"
- "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
- "was deleted or moved by %s in r%ld."),
- old_repos_relpath, old_rev,
- new_repos_relpath, new_rev,
- details->rev_author, details->deleted_rev);
+ {
+ if (details->move)
+ {
+ const char *description =
+ apr_psprintf(result_pool,
+ _("Directory switched from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
+ "was moved to '^/%s' by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->move->moved_to_repos_relpath,
+ details->rev_author, details->deleted_rev);
+ return append_moved_to_chain_description(description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ return apr_psprintf(result_pool,
+ _("Directory switched from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
+ "was deleted by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->rev_author, details->deleted_rev);
+ }
else if (victim_node_kind == svn_node_file ||
victim_node_kind == svn_node_symlink)
- return apr_psprintf(result_pool,
- _("File switched from\n"
- "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
- "deleted or moved by %s in r%ld."),
- old_repos_relpath, old_rev,
- new_repos_relpath, new_rev,
- details->rev_author, details->deleted_rev);
+ {
+ if (details->move)
+ {
+ const char *description =
+ apr_psprintf(result_pool,
+ _("File switched from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "moved to '^/%s' by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->move->moved_to_repos_relpath,
+ details->rev_author, details->deleted_rev);
+ return append_moved_to_chain_description(description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ return apr_psprintf(result_pool,
+ _("File switched from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "deleted by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->rev_author, details->deleted_rev);
+ }
else
- return apr_psprintf(result_pool,
- _("Item switched from\n"
- "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
- "deleted or moved by %s in r%ld."),
- old_repos_relpath, old_rev,
- new_repos_relpath, new_rev,
- details->rev_author, details->deleted_rev);
+ {
+ if (details->move)
+ {
+ const char *description =
+ apr_psprintf(result_pool,
+ _("Item switched from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "moved to '^/%s' by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->move->moved_to_repos_relpath,
+ details->rev_author, details->deleted_rev);
+ return append_moved_to_chain_description(description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ return apr_psprintf(result_pool,
+ _("Item switched from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "deleted by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->rev_author, details->deleted_rev);
+ }
}
}
@@ -1732,7 +2246,8 @@ describe_incoming_deletion_upon_merge(
svn_revnum_t old_rev,
const char *new_repos_relpath,
svn_revnum_t new_rev,
- apr_pool_t *result_pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
if (details->replacing_node_kind == svn_node_file ||
details->replacing_node_kind == svn_node_symlink)
@@ -1796,30 +2311,87 @@ describe_incoming_deletion_upon_merge(
else
{
if (victim_node_kind == svn_node_dir)
- return apr_psprintf(result_pool,
- _("Directory merged from\n"
- "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
- "deleted or moved by %s in r%ld."),
- old_repos_relpath, old_rev,
- new_repos_relpath, new_rev,
- details->rev_author, details->deleted_rev);
+ {
+ if (details->move)
+ {
+ const char *description =
+ apr_psprintf(result_pool,
+ _("Directory merged from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "moved to '^/%s' by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->move->moved_to_repos_relpath,
+ details->rev_author, details->deleted_rev);
+ return append_moved_to_chain_description(description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ return apr_psprintf(result_pool,
+ _("Directory merged from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "deleted by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->rev_author, details->deleted_rev);
+ }
else if (victim_node_kind == svn_node_file ||
victim_node_kind == svn_node_symlink)
- return apr_psprintf(result_pool,
- _("File merged from\n"
- "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
- "deleted or moved by %s in r%ld."),
- old_repos_relpath, old_rev,
- new_repos_relpath, new_rev,
- details->rev_author, details->deleted_rev);
+ {
+ if (details->move)
+ {
+ const char *description =
+ apr_psprintf(result_pool,
+ _("File merged from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "moved to '^/%s' by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->move->moved_to_repos_relpath,
+ details->rev_author, details->deleted_rev);
+ return append_moved_to_chain_description(description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ return apr_psprintf(result_pool,
+ _("File merged from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "deleted by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->rev_author, details->deleted_rev);
+ }
else
- return apr_psprintf(result_pool,
- _("Item merged from\n"
- "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
- "deleted or moved by %s in r%ld."),
- old_repos_relpath, old_rev,
- new_repos_relpath, new_rev,
- details->rev_author, details->deleted_rev);
+ {
+ if (details->move)
+ {
+ const char *description =
+ apr_psprintf(result_pool,
+ _("Item merged from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "moved to '^/%s' by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->move->moved_to_repos_relpath,
+ details->rev_author, details->deleted_rev);
+ return append_moved_to_chain_description(description,
+ details->move->next,
+ result_pool,
+ scratch_pool);
+ }
+ else
+ return apr_psprintf(result_pool,
+ _("Item merged from\n"
+ "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
+ "deleted by %s in r%ld."),
+ old_repos_relpath, old_rev,
+ new_repos_relpath, new_rev,
+ details->rev_author, details->deleted_rev);
+ }
}
}
@@ -1965,7 +2537,8 @@ conflict_tree_get_description_incoming_d
victim_node_kind,
old_rev,
new_rev,
- result_pool);
+ result_pool,
+ scratch_pool);
}
else /* details->added_rev != SVN_INVALID_REVNUM */
{
@@ -1984,7 +2557,8 @@ conflict_tree_get_description_incoming_d
old_rev,
new_repos_relpath,
new_rev,
- result_pool);
+ result_pool,
+ scratch_pool);
}
else /* details->added_rev != SVN_INVALID_REVNUM */
{
@@ -2000,12 +2574,13 @@ conflict_tree_get_description_incoming_d
if (details->deleted_rev != SVN_INVALID_REVNUM)
{
action = describe_incoming_deletion_upon_merge(details,
- victim_node_kind,
- old_repos_relpath,
- old_rev,
- new_repos_relpath,
- new_rev,
- result_pool);
+ victim_node_kind,
+ old_repos_relpath,
+ old_rev,
+ new_repos_relpath,
+ new_rev,
+ result_pool,
+ scratch_pool);
}
else /* details->added_rev != SVN_INVALID_REVNUM */
{
@@ -2026,12 +2601,15 @@ struct find_added_rev_baton
{
svn_revnum_t added_rev;
const char *repos_relpath;
+ const char *parent_repos_relpath;
apr_pool_t *pool;
};
/* Implements svn_location_segment_receiver_t.
* Finds the revision in which a node was added by tracing 'start'
- * revisions in location segments reported for the node. */
+ * revisions in location segments reported for the node.
+ * If the PARENT_REPOS_RELPATH in the baton is not NULL, only consider
+ * segments in which the node existed somwhere beneath this path. */
static svn_error_t *
find_added_rev(svn_location_segment_t *segment,
void *baton,
@@ -2041,8 +2619,13 @@ find_added_rev(svn_location_segment_t *s
if (segment->path) /* not interested in gaps */
{
- b->added_rev = segment->range_start;
- b->repos_relpath = apr_pstrdup(b->pool, segment->path);
+ if (b->parent_repos_relpath == NULL ||
+ svn_relpath_skip_ancestor(b->parent_repos_relpath,
+ segment->path) != NULL)
+ {
+ b->added_rev = segment->range_start;
+ b->repos_relpath = apr_pstrdup(b->pool, segment->path);
+ }
}
return SVN_NO_ERROR;
@@ -2081,6 +2664,7 @@ get_incoming_delete_details_for_reverse_
*details = apr_pcalloc(result_pool, sizeof(**details));
b.added_rev = SVN_INVALID_REVNUM;
b.repos_relpath = NULL;
+ b.parent_repos_relpath = NULL;
b.pool = scratch_pool;
/* Figure out when this node was added. */
SVN_ERR(svn_ra_get_location_segments(ra_session, "", old_rev,
@@ -2142,39 +2726,45 @@ conflict_tree_get_details_incoming_delet
{
if (old_rev < new_rev)
{
- svn_ra_session_t *ra_session;
- const char *url;
- const char *corrected_url;
+ const char *parent_repos_relpath;
svn_revnum_t deleted_rev;
- svn_string_t *author_revprop;
+ const char *deleted_rev_author;
+ svn_node_kind_t replacing_node_kind;
+ struct repos_move_info *move;
/* The update operation went forward in history. */
- url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
- scratch_pool);
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
- &corrected_url,
- url, NULL, NULL,
- FALSE,
- FALSE,
- conflict->ctx,
- scratch_pool,
- scratch_pool));
- SVN_ERR(svn_ra_get_deleted_rev(ra_session, "", old_rev, new_rev,
- &deleted_rev, scratch_pool));
- SVN_ERR(svn_ra_rev_prop(ra_session, deleted_rev,
- SVN_PROP_REVISION_AUTHOR,
- &author_revprop, scratch_pool));
+
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, &parent_repos_relpath,
+ NULL, NULL,
+ conflict->ctx->wc_ctx,
+ svn_dirent_dirname(
+ conflict->local_abspath,
+ scratch_pool),
+ scratch_pool,
+ scratch_pool));
+ SVN_ERR(find_revision_for_suspected_deletion(
+ &deleted_rev, &deleted_rev_author, &replacing_node_kind,
+ &move, conflict,
+ svn_dirent_basename(conflict->local_abspath, scratch_pool),
+ parent_repos_relpath, old_rev, new_rev,
+ NULL, SVN_INVALID_REVNUM, /* related to self */
+ conflict->pool, scratch_pool));
+ if (deleted_rev == SVN_INVALID_REVNUM)
+ {
+ /* We could not determine the revision in which the node was
+ * deleted. We cannot provide the required details so the best
+ * we can do is fall back to the default description. */
+ return SVN_NO_ERROR;
+ }
+
details = apr_pcalloc(conflict->pool, sizeof(*details));
details->deleted_rev = deleted_rev;
details->added_rev = SVN_INVALID_REVNUM;
details->repos_relpath = apr_pstrdup(conflict->pool,
new_repos_relpath);
- details->rev_author = apr_pstrdup(conflict->pool,
- author_revprop->data);
- /* Check for replacement. */
- SVN_ERR(svn_ra_check_path(ra_session, "", deleted_rev,
- &details->replacing_node_kind,
- scratch_pool));
+ details->rev_author = deleted_rev_author;
+ details->replacing_node_kind = replacing_node_kind;
+ details->move = move;
}
else /* new_rev < old_rev */
{
@@ -2194,6 +2784,7 @@ conflict_tree_get_details_incoming_delet
svn_revnum_t deleted_rev;
const char *deleted_rev_author;
svn_node_kind_t replacing_node_kind;
+ struct repos_move_info *move;
/* The switch/merge operation went forward in history.
*
@@ -2202,7 +2793,7 @@ conflict_tree_get_details_incoming_delet
* the revision which deleted the node. */
SVN_ERR(find_revision_for_suspected_deletion(
&deleted_rev, &deleted_rev_author, &replacing_node_kind,
- conflict,
+ &move, conflict,
svn_relpath_basename(new_repos_relpath, scratch_pool),
svn_relpath_dirname(new_repos_relpath, scratch_pool),
new_rev, old_rev, old_repos_relpath, old_rev,
@@ -2223,6 +2814,7 @@ conflict_tree_get_details_incoming_delet
details->rev_author = apr_pstrdup(conflict->pool,
deleted_rev_author);
details->replacing_node_kind = replacing_node_kind;
+ details->move = move;
}
else /* new_rev < old_rev */
{
@@ -2262,6 +2854,10 @@ struct conflict_tree_incoming_add_detail
/* Authors who committed ADDED_REV/DELETED_REV. */
const char *added_rev_author;
const char *deleted_rev_author;
+
+ /* Move information, in case the item was not added/deleted but moved here
+ * or moved away. Else NULL. */
+ struct repos_move_info *move;
};
/* Implements tree_conflict_get_details_func_t.
@@ -2323,6 +2919,7 @@ conflict_tree_get_details_incoming_add(s
details = apr_pcalloc(conflict->pool, sizeof(*details));
b.added_rev = SVN_INVALID_REVNUM;
b.repos_relpath = NULL;
+ b.parent_repos_relpath = NULL;
b.pool = scratch_pool;
/* Figure out when this node was added. */
SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
@@ -2386,6 +2983,7 @@ conflict_tree_get_details_incoming_add(s
details = apr_pcalloc(conflict->pool, sizeof(*details));
b.added_rev = SVN_INVALID_REVNUM;
b.repos_relpath = NULL;
+ b.parent_repos_relpath = NULL;
b.pool = scratch_pool;
/* Figure out when this node was added. */
SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
@@ -2409,36 +3007,37 @@ conflict_tree_get_details_incoming_add(s
* This addition is in fact a deletion, applied in reverse,
* which happened on the branch we merged from.
* Find the revision which deleted the node. */
- svn_ra_session_t *ra_session;
- const char *url;
- const char *corrected_url;
- svn_string_t *author_revprop;
svn_revnum_t deleted_rev;
+ const char *deleted_rev_author;
+ svn_node_kind_t replacing_node_kind;
+ struct repos_move_info *move;
+
+ SVN_ERR(find_revision_for_suspected_deletion(
+ &deleted_rev, &deleted_rev_author, &replacing_node_kind,
+ &move, conflict, svn_relpath_basename(new_repos_relpath,
+ scratch_pool),
+ svn_relpath_dirname(new_repos_relpath, scratch_pool),
+ new_rev, old_rev,
+ NULL, SVN_INVALID_REVNUM, /* related to self */
+ conflict->pool, scratch_pool));
+ if (deleted_rev == SVN_INVALID_REVNUM)
+ {
+ /* We could not determine the revision in which the node was
+ * deleted. We cannot provide the required details so the best
+ * we can do is fall back to the default description. */
+ return SVN_NO_ERROR;
+ }
- url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
- scratch_pool);
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
- &corrected_url,
- url, NULL, NULL,
- FALSE,
- FALSE,
- conflict->ctx,
- scratch_pool,
- scratch_pool));
- SVN_ERR(svn_ra_get_deleted_rev(ra_session, "", new_rev, old_rev,
- &deleted_rev, scratch_pool));
- SVN_ERR(svn_ra_rev_prop(ra_session, deleted_rev,
- SVN_PROP_REVISION_AUTHOR,
- &author_revprop, scratch_pool));
details = apr_pcalloc(conflict->pool, sizeof(*details));
details->repos_relpath = apr_pstrdup(conflict->pool,
new_repos_relpath);
details->deleted_rev = deleted_rev;
details->deleted_rev_author = apr_pstrdup(conflict->pool,
- author_revprop->data);
+ deleted_rev_author);
details->added_rev = SVN_INVALID_REVNUM;
details->added_rev_author = NULL;
+ details->move = move;
}
}
else
@@ -4024,6 +4623,8 @@ resolve_merge_incoming_added_dir_merge(s
{
const char *repos_root_url;
const char *repos_uuid;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
const char *incoming_new_repos_relpath;
svn_revnum_t incoming_new_pegrev;
const char *local_abspath;
@@ -4044,7 +4645,7 @@ resolve_merge_incoming_added_dir_merge(s
return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
_("Conflict resolution option '%d' requires "
"details for tree conflict at '%s' to be "
- "fetched from the repository."),
+ "fetched from the repository"),
option->id,
svn_dirent_local_style(local_abspath,
scratch_pool));
@@ -4057,15 +4658,44 @@ resolve_merge_incoming_added_dir_merge(s
details->repos_relpath,
scratch_pool);
revision1.kind = svn_opt_revision_number;
- revision1.value.number = details->added_rev;
+ 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));
- source2 = svn_path_url_add_component2(repos_root_url,
- incoming_new_repos_relpath,
- scratch_pool);
- revision2.kind = svn_opt_revision_number;
- revision2.value.number = incoming_new_pegrev;
+ if (incoming_old_pegrev < incoming_new_pegrev) /* forward merge */
+ {
+ if (details->added_rev == SVN_INVALID_REVNUM)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Could not determine when '%s' was "
+ "added the repository"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ revision1.value.number = details->added_rev;
+
+ source2 = svn_path_url_add_component2(repos_root_url,
+ incoming_new_repos_relpath,
+ scratch_pool);
+ revision2.kind = svn_opt_revision_number;
+ revision2.value.number = incoming_new_pegrev;
+ }
+ else /* reverse-merge */
+ {
+ if (details->deleted_rev == SVN_INVALID_REVNUM)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Could not determine when '%s' was "
+ "deleted from the repository"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ revision1.value.number = details->deleted_rev;
+
+ source2 = svn_path_url_add_component2(repos_root_url,
+ incoming_old_repos_relpath,
+ scratch_pool);
+ revision2.kind = svn_opt_revision_number;
+ revision2.value.number = incoming_old_pegrev;
+ }
/* ### The following WC modifications should be atomic. */
SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
@@ -4122,6 +4752,559 @@ resolve_merge_incoming_added_dir_merge(s
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. */
+static svn_error_t *
+merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ svn_boolean_t merge_dirs,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_session_t *ra_session;
+ const char *url;
+ const char *corrected_url;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ const char *local_abspath;
+ const char *lock_abspath;
+ svn_client_ctx_t *ctx = conflict->ctx;
+ 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);
+
+ /* Find the URL of the incoming added directory in the repository. */
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
+ conflict, scratch_pool,
+ scratch_pool));
+ url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
+ scratch_pool);
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+ url, NULL, NULL, FALSE, FALSE,
+ conflict->ctx, scratch_pool,
+ scratch_pool));
+ 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(©_src_revnum, ×tamp_sleep,
+ url, tmp_abspath,
+ ©_src_peg_revision,
+ ©_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,
+ svn_dirent_dirname(
+ local_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ /* Remove the working directory. */
+ err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool);
+ 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 */,
+ ctx->cancel_func, ctx->cancel_baton,
+ 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,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ 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);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_add,
+ scratch_pool);
+ notify->kind = svn_node_dir;
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ /* Resolve to current working copy state.
+ * svn_client__merge_locked() requires this. */
+ err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (merge_dirs)
+ {
+ svn_client__conflict_report_t *conflict_report;
+ const char *source1;
+ svn_opt_revision_t revision1;
+ const char *source2;
+ svn_opt_revision_t revision2;
+ svn_revnum_t base_revision;
+ const char *base_repos_relpath;
+ struct find_added_rev_baton b;
+
+ /* Find the URL and revision of the directory we have just replaced. */
+ err = svn_wc__node_get_base(NULL, &base_revision, &base_repos_relpath,
+ NULL, NULL, NULL, ctx->wc_ctx, local_abspath,
+ FALSE, scratch_pool, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ url = svn_path_url_add_component2(repos_root_url, base_repos_relpath,
+ scratch_pool);
+
+ /* Trace the replaced directory's history to its origin. */
+ err = svn_ra_reparent(ra_session, url, scratch_pool);
+ if (err)
+ goto unlock_wc;
+ b.added_rev = SVN_INVALID_REVNUM;
+ b.repos_relpath = NULL;
+ b.parent_repos_relpath = svn_relpath_dirname(base_repos_relpath,
+ scratch_pool);
+ b.pool = scratch_pool;
+ err = svn_ra_get_location_segments(ra_session, "", base_revision,
+ base_revision, SVN_INVALID_REVNUM,
+ find_added_rev, &b,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (b.added_rev == SVN_INVALID_REVNUM)
+ {
+ err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Could not determine the revision in "
+ "which '^/%s' was added to the "
+ "repository.\n"),
+ base_repos_relpath);
+ goto unlock_wc;
+ }
+
+ /* Merge the replaced directory into the directory which replaced it.
+ * We do not need to consider a reverse-merge here since the source of
+ * this merge was part of the merge target working copy, not a branch
+ * in the repository. */
+ source1 = url;
+ revision1.kind = svn_opt_revision_number;
+ /* ### Our merge logic doesn't support the merge -c ADDED_REV case.
+ * ### It errors out with 'path not found', unlike diff -c ADDED_REV. */
+ if (b.added_rev == base_revision)
+ revision1.value.number = b.added_rev - 1; /* merge -c ADDED_REV */
+ else
+ revision1.value.number = b.added_rev; /* merge -r ADDED_REV:BASE_REV */
+ source2 = url;
+ revision2.kind = svn_opt_revision_number;
+ revision2.value.number = base_revision;
+
+ err = svn_client__merge_locked(&conflict_report,
+ source1, &revision1,
+ source2, &revision2,
+ local_abspath, svn_depth_infinity,
+ TRUE, TRUE, /* do a no-ancestry merge */
+ FALSE, FALSE, FALSE,
+ FALSE, /* no need to allow mixed-rev */
+ NULL, ctx, scratch_pool, scratch_pool);
+ err = svn_error_compose_create(err,
+ svn_client__make_merge_conflict_error(
+ conflict_report, scratch_pool));
+ }
+
+unlock_wc:
+ err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ scratch_pool));
+ svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
+ SVN_ERR(err);
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(
+ local_abspath,
+ svn_wc_notify_resolved_tree,
+ scratch_pool);
+
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ conflict->resolution_tree = svn_client_conflict_option_get_id(option);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements conflict_option_resolve_func_t. */
+static svn_error_t *
+resolve_merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(merge_incoming_added_dir_replace(option,
+ conflict,
+ FALSE,
+ scratch_pool));
+}
+
+/* Implements conflict_option_resolve_func_t. */
+static svn_error_t *
+resolve_merge_incoming_added_dir_replace_and_merge(
+ svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(merge_incoming_added_dir_replace(option,
+ conflict,
+ TRUE,
+ scratch_pool));
+}
+
+/* Verify the local working copy state matches what we expect when an
+ * incoming deletion tree conflict exists.
+ * We assume update/merge/switch operations leave the working copy in a
+ * state which prefers the local change and cancels the deletion.
+ * Run a quick sanity check and error out if it looks as if the
+ * working copy was modified since, even though it's not easy to make
+ * such modifications without also clearing the conflict marker. */
+static svn_error_t *
+verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict,
+ svn_client_conflict_option_t *option,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ const char *wcroot_abspath;
+ svn_wc_operation_t operation;
+ svn_client_ctx_t *ctx = conflict->ctx;
+
+ local_abspath = svn_client_conflict_get_local_abspath(conflict);
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ local_abspath, scratch_pool,
+ scratch_pool));
+ operation = svn_client_conflict_get_operation(conflict);
+
+ if (operation == svn_wc_operation_update ||
+ operation == svn_wc_operation_switch)
+ {
+ struct conflict_tree_incoming_delete_details *details;
+ svn_boolean_t is_copy;
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_repos_relpath;
+
+ details = conflict->tree_conflict_incoming_details;
+ if (details == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Conflict resolution option '%d' requires "
+ "details for tree conflict at '%s' to be "
+ "fetched from the repository."),
+ option->id,
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ /* Ensure that the item is a copy of itself from before it was deleted.
+ * Update and switch are supposed to set this up when flagging the
+ * conflict. */
+ SVN_ERR(svn_wc__node_get_origin(&is_copy, ©from_rev,
+ ©from_repos_relpath,
+ NULL, NULL, NULL, NULL,
+ ctx->wc_ctx, local_abspath, FALSE,
+ scratch_pool, scratch_pool));
+ if (!is_copy)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' by "
+ "ignoring the incoming deletion "
+ "(expected a copied item, but the item "
+ "is not a copy)"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath,
+ conflict->local_abspath),
+ scratch_pool));
+ else if (details->deleted_rev == SVN_INVALID_REVNUM &&
+ details->added_rev == SVN_INVALID_REVNUM)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Could not find the revision in which '%s' "
+ "was deleted from the repository"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath,
+ conflict->local_abspath),
+ scratch_pool));
+ else if (details->deleted_rev != SVN_INVALID_REVNUM &&
+ copyfrom_rev >= details->deleted_rev)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' by "
+ "ignoring the incoming deletion (expected "
+ "an item copied from a revision smaller "
+ "than r%ld, but the item was copied from "
+ "r%ld)"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath, conflict->local_abspath),
+ scratch_pool),
+ details->deleted_rev, copyfrom_rev);
+
+ else if (details->added_rev != SVN_INVALID_REVNUM &&
+ copyfrom_rev < details->added_rev)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' by "
+ "ignoring the incoming deletion (expected "
+ "an item copied from a revision larger than "
+ "r%ld, but the item was copied from r%ld)"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath, conflict->local_abspath),
+ scratch_pool),
+ details->added_rev, copyfrom_rev);
+
+ else if (operation == svn_wc_operation_update &&
+ strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' by "
+ "ignoring the incoming deletion (expected "
+ "an item copied from '^/%s', but the item "
+ "was copied from '^/%s@%ld')"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath, conflict->local_abspath),
+ scratch_pool),
+ details->repos_relpath,
+ copyfrom_repos_relpath, copyfrom_rev);
+ else if (operation == svn_wc_operation_switch)
+ {
+ const char *old_repos_relpath;
+
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &old_repos_relpath, NULL, NULL, conflict,
+ scratch_pool, scratch_pool));
+
+ if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' "
+ "by ignoring the incoming deletion "
+ "(expected an item copied from '^/%s', "
+ "but the item was copied from "
+ "'^/%s@%ld')"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath,
+ conflict->local_abspath),
+ scratch_pool),
+ old_repos_relpath,
+ copyfrom_repos_relpath, copyfrom_rev);
+ }
+ }
+ else if (operation == svn_wc_operation_merge)
+ {
+ svn_node_kind_t victim_node_kind;
+ svn_node_kind_t on_disk_kind;
+
+ /* For merge, all we can do is ensure that the item still exists. */
+ victim_node_kind =
+ svn_client_conflict_tree_get_victim_node_kind(conflict);
+ SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
+
+ if (victim_node_kind != on_disk_kind)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' by "
+ "ignoring the incoming deletion (expected "
+ "node kind '%s' but found '%s')"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath, conflict->local_abspath),
+ scratch_pool),
+ svn_node_kind_to_word(victim_node_kind),
+ svn_node_kind_to_word(on_disk_kind));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements conflict_option_resolve_func_t. */
+static svn_error_t *
+resolve_incoming_delete_ignore(svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_conflict_option_id_t option_id;
+ const char *local_abspath;
+ const char *lock_abspath;
+ svn_client_ctx_t *ctx = conflict->ctx;
+ svn_error_t *err;
+
+ option_id = svn_client_conflict_option_get_id(option);
+ local_abspath = svn_client_conflict_get_local_abspath(conflict);
+
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
+ local_abspath,
+ scratch_pool, scratch_pool));
+
+ err = verify_local_state_for_incoming_delete(conflict, option, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Resolve to the current working copy state. */
+ err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
+
+ /* svn_wc__del_tree_conflict doesn't handle notification for us */
+ if (ctx->notify_func2)
+ ctx->notify_func2(ctx->notify_baton2,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_resolved_tree,
+ scratch_pool),
+ scratch_pool);
+
+unlock_wc:
+ err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ scratch_pool));
+ SVN_ERR(err);
+
+ conflict->resolution_tree = option_id;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements conflict_option_resolve_func_t. */
+static svn_error_t *
+resolve_incoming_delete_accept(svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_conflict_option_id_t option_id;
+ const char *local_abspath;
+ const char *lock_abspath;
+ svn_client_ctx_t *ctx = conflict->ctx;
+ svn_error_t *err;
+
+ option_id = svn_client_conflict_option_get_id(option);
+ local_abspath = svn_client_conflict_get_local_abspath(conflict);
+
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
+ local_abspath,
+ scratch_pool, scratch_pool));
+
+ err = verify_local_state_for_incoming_delete(conflict, option, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Delete the tree conflict victim. Marks the conflict resolved. */
+ err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ ctx->notify_func2(ctx->notify_baton2,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_resolved_tree,
+ scratch_pool),
+ scratch_pool);
+
+unlock_wc:
+ err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ scratch_pool));
+ SVN_ERR(err);
+
+ conflict->resolution_tree = option_id;
+
+ return SVN_NO_ERROR;
+}
+
/* Resolver options for a text conflict */
static const svn_client_conflict_option_t text_conflict_options[] =
{
@@ -4753,6 +5936,200 @@ configure_option_merge_incoming_added_di
return SVN_NO_ERROR;
}
+/* Configure 'incoming added dir replace' resolution option for a tree
+ * conflict. */
+static svn_error_t *
+configure_option_merge_incoming_added_dir_replace(
+ svn_client_conflict_t *conflict,
+ 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;
+ svn_node_kind_t victim_node_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);
+ victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
+ 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_dir &&
+ incoming_new_kind == svn_node_dir &&
+ incoming_change == svn_wc_conflict_action_add &&
+ local_change == svn_wc_conflict_reason_obstructed)
+ {
+ svn_client_conflict_option_t *option;
+ const char *wcroot_abspath;
+
[... 179 lines stripped ...]