Modified: subversion/branches/authzperf/subversion/libsvn_client/conflicts.c URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_client/conflicts.c?rev=1764707&r1=1764706&r2=1764707&view=diff ============================================================================== --- subversion/branches/authzperf/subversion/libsvn_client/conflicts.c (original) +++ subversion/branches/authzperf/subversion/libsvn_client/conflicts.c Thu Oct 13 15:25:15 2016 @@ -38,6 +38,9 @@ #include "svn_hash.h" #include "svn_sorts.h" #include "client.h" + +#include "private/svn_diff_tree.h" +#include "private/svn_ra_private.h" #include "private/svn_sorts_private.h" #include "private/svn_token.h" #include "private/svn_wc_private.h" @@ -53,6 +56,7 @@ typedef svn_error_t *(*tree_conflict_get_description_func_t)( const char **change_description, svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool); @@ -60,16 +64,16 @@ typedef svn_error_t *(*tree_conflict_get * This function may contact the repository. */ typedef svn_error_t *(*tree_conflict_get_details_func_t)( svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *scratch_pool); struct svn_client_conflict_t { const char *local_abspath; - svn_client_ctx_t *ctx; apr_hash_t *prop_conflicts; /* Indicate which options were chosen to resolve a text or tree conflict - * on the conflited node. */ + * on the conflicted node. */ svn_client_conflict_option_id_t resolution_text; svn_client_conflict_option_id_t resolution_tree; @@ -112,6 +116,7 @@ struct svn_client_conflict_t typedef svn_error_t *(*conflict_option_resolve_func_t)( svn_client_conflict_option_t *option, svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *scratch_pool); struct svn_client_conflict_option_t @@ -122,6 +127,9 @@ struct svn_client_conflict_option_t svn_client_conflict_t *conflict; conflict_option_resolve_func_t do_resolve_func; + /* The pool this option was allocated from. */ + apr_pool_t *pool; + /* Data which is specific to particular conflicts and options. */ union { struct { @@ -235,49 +243,60 @@ static const svn_token_map_t map_conflic }; /* Describes a server-side move (really a copy+delete within the same - * revision) which was identified by scanning the revision log. */ + * revision) which was identified by scanning the revision log. + * This structure can represent one or more "chains" of moves, i.e. + * multiple move operations which occurred across a range of revisions. */ 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 repository relpath the node was moved from in this revision. */ + const char *moved_from_repos_relpath; + + /* The repository relpath the node was moved to in this revision. */ + const char *moved_to_repos_relpath; + /* 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. */ + /* Prev pointer. NULL if no prior move exists in the chain. */ struct repos_move_info *prev; - struct repos_move_info *next; + + /* An array of struct repos_move_info * elements, each representing + * a possible way forward in the move chain. NULL if no next move + * exists in this chain. If the deleted node was copied only once in + * this revision, then this array has only one element and the move + * chain does not fork. But if this revision contains multiple copies of + * the deleted node, each of these copies appears as an element of this + * array, and each element represents a different path the next move + * might have taken. */ + apr_array_header_t *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. */ +/* Set *RELATED to true if the deleted node DELETED_REPOS_RELPATH@DELETED_REV + * is an ancestor of the copied node COPYFROM_PATH@COPYFROM_REV. + * If CHECK_LAST_CHANGED_REV is non-zero, also ensure that the copied node + * is a copy of the deleted node's last-changed revision's content, rather + * than a copy of some older content. If it's not, set *RELATED to false. */ static svn_error_t * check_move_ancestry(svn_boolean_t *related, + svn_ra_session_t *ra_session, 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, + svn_boolean_t check_last_changed_rev, 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; + const char *old_session_url; location_revisions = apr_array_make(scratch_pool, 1, sizeof(svn_revnum_t)); APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = copyfrom_rev; @@ -286,11 +305,8 @@ check_move_ancestry(svn_boolean_t *relat 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_client__ensure_ra_session_url(&old_session_url, ra_session, + deleted_url, scratch_pool)); SVN_ERR(svn_ra_get_locations(ra_session, &locations, "", deleted_rev - 1, location_revisions, scratch_pool)); @@ -301,9 +317,34 @@ check_move_ancestry(svn_boolean_t *relat { if (deleted_location[0] == '/') deleted_location++; - *related = (strcmp(deleted_location, copyfrom_path) == 0); + if (strcmp(deleted_location, copyfrom_path) != 0) + { + *related = FALSE; + return SVN_NO_ERROR; + } + } + else + { + *related = FALSE; + return SVN_NO_ERROR; + } + + if (check_last_changed_rev) + { + svn_dirent_t *dirent; + + /* Verify that copyfrom_rev >= last-changed revision of the + * deleted node. */ + SVN_ERR(svn_ra_stat(ra_session, "", deleted_rev - 1, &dirent, + scratch_pool)); + if (dirent == NULL || copyfrom_rev < dirent->created_rev) + { + *related = FALSE; + return SVN_NO_ERROR; + } } + *related = TRUE; return SVN_NO_ERROR; } @@ -314,15 +355,16 @@ struct copy_info { }; /* Update MOVES_TABLE and MOVED_PATHS based on information from - * revision data in LOG_ENTRY, COPIES, and DELETED_PATHS. */ + * revision data in LOG_ENTRY, COPIES, and DELETED_PATHS. + * Use RA_SESSION to perform the necessary requests. */ static svn_error_t * -find_moves_in_revision(apr_hash_t *moves_table, +find_moves_in_revision(svn_ra_session_t *ra_session, + 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) { @@ -364,20 +406,15 @@ find_moves_in_revision(apr_hash_t *moves * 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, + SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url, deleted_repos_relpath, log_entry->revision, copy->copyfrom_path, copy->copyfrom_rev, - ctx, iterpool)); + TRUE, 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, @@ -398,18 +435,23 @@ find_moves_in_revision(apr_hash_t *moves /* 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, + SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url, next_move->moved_from_repos_relpath, next_move->rev, move->moved_from_repos_relpath, move->copyfrom_rev, - ctx, iterpool)); + FALSE, iterpool)); if (related) { SVN_ERR_ASSERT(move->rev < next_move->rev); /* Prepend this move to the linked list. */ - move->next = next_move; + if (move->next == NULL) + move->next = apr_array_make( + result_pool, 1, + sizeof (struct repos_move_info *)); + APR_ARRAY_PUSH(move->next, + struct repos_move_info *) = next_move; next_move->prev = move; } } @@ -445,6 +487,7 @@ struct find_deleted_rev_baton const char *repos_root_url; const char *repos_uuid; svn_client_ctx_t *ctx; + const char *victim_abspath; /* for notifications */ /* Variables below are results for the caller of svn_ra_get_log2(). */ svn_revnum_t deleted_rev; @@ -500,14 +543,50 @@ struct find_deleted_rev_baton /* 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; + + /* Extra RA session that can be used to make additional requests. */ + svn_ra_session_t *extra_ra_session; }; +/* Find the youngest common ancestor of REPOS_RELPATH1@PEG_REV1 and + * REPOS_RELPATH2@PEG_REV2. Return the result in *YCA_LOC. + * Set *YCA_LOC to NULL if no common ancestor exists. */ +static svn_error_t * +find_yca(svn_client__pathrev_t **yca_loc, + const char *repos_relpath1, + svn_revnum_t peg_rev1, + const char *repos_relpath2, + svn_revnum_t peg_rev2, + const char *repos_root_url, + const char *repos_uuid, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client__pathrev_t *loc1; + svn_client__pathrev_t *loc2; + + *yca_loc = NULL; + + loc1 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid, + peg_rev1, repos_relpath1, + scratch_pool); + loc2 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid, + peg_rev2, repos_relpath2, + scratch_pool); + SVN_ERR(svn_client__get_youngest_common_ancestor(yca_loc, loc1, loc2, NULL, + ctx, result_pool, + scratch_pool)); + + return SVN_NO_ERROR; +} + /* Implements svn_log_entry_receiver_t. * * 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. + * by raising SVN_ERR_CEASE_INVOCATION. * * If no such revision can be found, leave BATON->DELETED_REV and * BATON->REPLACING_NODE_KIND alone. @@ -520,7 +599,7 @@ struct find_deleted_rev_baton * works in cases where we do not already know a revision in which the deleted * node once used to exist. * - * If the node node was moved, rather than deleted, return move information + * If the node was moved, rather than deleted, return move information * in BATON->MOVE. */ static svn_error_t * @@ -535,6 +614,18 @@ find_deleted_rev(void *baton, apr_array_header_t *deleted_paths; apr_hash_t *copies; + if (b->ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify( + b->victim_abspath, + svn_wc_notify_tree_conflict_details_progress, + scratch_pool), + notify->revision = log_entry->revision; + b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool); + } + /* No paths were changed in this revision. Nothing to do. */ if (! log_entry->changed_paths2) return SVN_NO_ERROR; @@ -606,27 +697,32 @@ find_deleted_rev(void *baton, 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; + svn_client__pathrev_t *yca_loc; + svn_error_t *err; /* 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)); + err = find_yca(&yca_loc, + b->related_repos_relpath, + b->related_repos_peg_rev, + b->deleted_repos_relpath, + log_entry->revision - 1, + b->repos_root_url, b->repos_uuid, + b->ctx, iterpool, iterpool); + if (err) + { + /* ### Happens for moves within other moves and copies. */ + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + deleted_node_found = (yca_loc != NULL); } @@ -649,14 +745,15 @@ find_deleted_rev(void *baton, svn_pool_destroy(iterpool); /* Check for moves in this revision */ - SVN_ERR(find_moves_in_revision(b->moves_table, b->moved_paths, + SVN_ERR(find_moves_in_revision(b->extra_ra_session, + b->moves_table, b->moved_paths, log_entry, copies, deleted_paths, - b->repos_root_url, b->ctx, + b->repos_root_url, 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_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); } return SVN_NO_ERROR; @@ -667,6 +764,7 @@ find_deleted_rev(void *baton, static svn_error_t * describe_local_file_node_change(const char **description, svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -726,7 +824,7 @@ describe_local_file_node_change(const ch const char *moved_to_abspath; SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -745,7 +843,7 @@ describe_local_file_node_change(const ch const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -778,7 +876,7 @@ describe_local_file_node_change(const ch const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -800,7 +898,7 @@ describe_local_file_node_change(const ch const char *moved_from_abspath; SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -819,7 +917,7 @@ describe_local_file_node_change(const ch const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -851,7 +949,7 @@ describe_local_file_node_change(const ch const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -879,6 +977,7 @@ describe_local_file_node_change(const ch static svn_error_t * describe_local_dir_node_change(const char **description, svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -915,8 +1014,7 @@ describe_local_dir_node_change(const cha case svn_wc_conflict_reason_missing: if (operation == svn_wc_operation_update || operation == svn_wc_operation_switch) - *description = _("No such directory was found in the working " - "copy."); + *description = _("No such directory was found in the working copy."); else if (operation == svn_wc_operation_merge) { /* ### display deleted revision */ @@ -940,7 +1038,7 @@ describe_local_dir_node_change(const cha const char *moved_to_abspath; SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -959,7 +1057,7 @@ describe_local_dir_node_change(const cha const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -992,7 +1090,7 @@ describe_local_dir_node_change(const cha const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -1014,7 +1112,7 @@ describe_local_dir_node_change(const cha const char *moved_from_abspath; SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -1033,7 +1131,7 @@ describe_local_dir_node_change(const cha const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -1066,7 +1164,7 @@ describe_local_dir_node_change(const cha const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, - conflict->ctx->wc_ctx, + ctx->wc_ctx, conflict->local_abspath, scratch_pool, scratch_pool)); @@ -1095,14 +1193,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. + * If the deleted node was moved, provide heads of move chains in *MOVES. + * If the node was not moved,set *MOVES 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, + struct apr_array_header_t **moves, svn_client_conflict_t *conflict, const char *deleted_basename, const char *parent_repos_relpath, @@ -1110,6 +1208,7 @@ find_revision_for_suspected_deletion(svn svn_revnum_t end_rev, const char *related_repos_relpath, svn_revnum_t related_peg_rev, + svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -1121,8 +1220,11 @@ find_revision_for_suspected_deletion(svn const char *repos_root_url; const char *repos_uuid; struct find_deleted_rev_baton b; + const char *victim_abspath; svn_error_t *err; + SVN_ERR_ASSERT(start_rev > end_rev); + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid, conflict, scratch_pool, scratch_pool)); @@ -1130,7 +1232,7 @@ find_revision_for_suspected_deletion(svn scratch_pool); SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, url, NULL, NULL, FALSE, FALSE, - conflict->ctx, scratch_pool, + ctx, scratch_pool, scratch_pool)); paths = apr_array_make(scratch_pool, 1, sizeof(const char *)); @@ -1139,6 +1241,8 @@ find_revision_for_suspected_deletion(svn revprops = apr_array_make(scratch_pool, 1, sizeof(const char *)); APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + b.victim_abspath = victim_abspath; b.deleted_repos_relpath = svn_relpath_join(parent_repos_relpath, deleted_basename, scratch_pool); b.related_repos_relpath = related_repos_relpath; @@ -1147,10 +1251,12 @@ find_revision_for_suspected_deletion(svn b.replacing_node_kind = svn_node_unknown; b.repos_root_url = repos_root_url; b.repos_uuid = repos_uuid; - b.ctx = conflict->ctx; + b.ctx = ctx; b.moves_table = apr_hash_make(result_pool); b.moved_paths = apr_hash_make(scratch_pool); b.result_pool = result_pool; + SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL, + scratch_pool, scratch_pool)); err = svn_ra_get_log2(ra_session, paths, start_rev, end_rev, 0, /* no limit */ @@ -1162,8 +1268,9 @@ find_revision_for_suspected_deletion(svn scratch_pool); if (err) { - if (err->apr_err == SVN_ERR_CANCELLED && + if (err->apr_err == SVN_ERR_CEASE_INVOCATION && b.deleted_rev != SVN_INVALID_REVNUM) + { /* Log operation was aborted because we found deleted rev. */ svn_error_clear(err); @@ -1179,41 +1286,46 @@ find_revision_for_suspected_deletion(svn *deleted_rev = SVN_INVALID_REVNUM; *deleted_rev_author = NULL; *replacing_node_kind = svn_node_unknown; - *move = NULL; + *moves = NULL; return SVN_NO_ERROR; } else { - apr_array_header_t *moves; + apr_array_header_t *all_moves_in_deleted_rev; *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) + /* Look for a moves which affect the deleted node. */ + all_moves_in_deleted_rev = apr_hash_get(b.moves_table, &b.deleted_rev, + sizeof(svn_revnum_t)); + if (all_moves_in_deleted_rev) { int i; - for (i = 0; i < moves->nelts; i++) + *moves = apr_array_make(result_pool, 1, sizeof(const char *)); + for (i = 0; i < all_moves_in_deleted_rev->nelts; i++) { struct repos_move_info *this_move; - this_move = APR_ARRAY_IDX(moves, i, struct repos_move_info *); + this_move = APR_ARRAY_IDX(all_moves_in_deleted_rev, 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; + APR_ARRAY_PUSH(*moves, struct repos_move_info *) = this_move; } } + + /* If we didn't find any applicable moves, return NULL. */ + if ((*moves)->nelts == 0) + *moves = NULL; } else - *move = NULL; + *moves = NULL; } return SVN_NO_ERROR; @@ -1228,14 +1340,19 @@ 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; + /* The path which was deleted relative to the repository root. */ + const char *deleted_repos_relpath; + + /* Move information. 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. */ + apr_array_header_t *moves; }; /* Implements tree_conflict_get_details_func_t. */ static svn_error_t * conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { const char *old_repos_relpath; @@ -1248,7 +1365,9 @@ 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; + apr_array_header_t *moves; + const char *related_repos_relpath; + svn_revnum_t related_peg_rev; /* We only handle merges here. */ if (svn_client_conflict_get_operation(conflict) != svn_wc_operation_merge) @@ -1268,18 +1387,95 @@ conflict_tree_get_details_local_missing( scratch_pool); SVN_ERR(svn_wc__node_get_repos_info(NULL, &parent_repos_relpath, NULL, NULL, - conflict->ctx->wc_ctx, + ctx->wc_ctx, svn_dirent_dirname( conflict->local_abspath, scratch_pool), scratch_pool, scratch_pool)); + + /* Pick the younger incoming node as our 'related node' which helps + * pin-pointing the deleted conflict victim in history. */ + related_repos_relpath = + (old_rev < new_rev ? new_repos_relpath : old_repos_relpath); + related_peg_rev = (old_rev < new_rev ? new_rev : old_rev); + + /* Make sure we're going to search the related node in a revision where + * it exists. The younger incoming node might have been deleted in HEAD. */ + if (related_repos_relpath != NULL && related_peg_rev != SVN_INVALID_REVNUM) + { + const char *repos_root_url; + const char *repos_uuid; + const char *related_url; + const char *corrected_url; + svn_node_kind_t related_node_kind; + svn_ra_session_t *ra_session; + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, + &repos_uuid, + conflict, + scratch_pool, scratch_pool)); + related_url = svn_path_url_add_component2(repos_root_url, + related_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, + &corrected_url, + related_url, NULL, + NULL, + FALSE, + FALSE, + ctx, + scratch_pool, + scratch_pool)); + SVN_ERR(svn_ra_check_path(ra_session, "", related_peg_rev, + &related_node_kind, scratch_pool)); + if (related_node_kind == svn_node_none) + { + svn_revnum_t related_deleted_rev; + const char *related_deleted_rev_author; + svn_node_kind_t related_replacing_node_kind; + const char *related_basename; + const char *related_parent_repos_relpath; + apr_array_header_t *related_moves; + const char *older_incoming_repos_relpath; + svn_revnum_t older_incoming_peg_rev; + + /* Looks like the younger incoming node, which we'd like to use as + * our 'related node', was also deleted. Try to find its deleted + * revision so we can calculate a peg revision at which it exists. + * The younger incoming node is related to the older incoming node, + * so we can use the older incoming node to guide us in our search. */ + related_basename = svn_relpath_basename(related_repos_relpath, + scratch_pool); + related_parent_repos_relpath = + svn_relpath_dirname(related_repos_relpath, scratch_pool); + older_incoming_repos_relpath = + (old_rev < new_rev ? old_repos_relpath : new_repos_relpath); + older_incoming_peg_rev = (old_rev < new_rev ? old_rev : new_rev); + SVN_ERR(find_revision_for_suspected_deletion( + &related_deleted_rev, &related_deleted_rev_author, + &related_replacing_node_kind, &related_moves, + conflict, related_basename, + related_parent_repos_relpath, + old_rev < new_rev ? new_rev : old_rev, 0, + older_incoming_repos_relpath, older_incoming_peg_rev, + ctx, conflict->pool, scratch_pool)); + + /* If we can't find a related node, bail. */ + if (related_deleted_rev == SVN_INVALID_REVNUM) + return SVN_NO_ERROR; + + /* The node should exist in the revision before it was deleted. */ + related_peg_rev = related_deleted_rev - 1; + } + } + SVN_ERR(find_revision_for_suspected_deletion( - &deleted_rev, &deleted_rev_author, &replacing_node_kind, &move, + &deleted_rev, &deleted_rev_author, &replacing_node_kind, &moves, 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, + related_repos_relpath, + related_peg_rev, ctx, conflict->pool, scratch_pool)); if (deleted_rev == SVN_INVALID_REVNUM) @@ -1288,7 +1484,10 @@ conflict_tree_get_details_local_missing( details = apr_pcalloc(conflict->pool, sizeof(*details)); details->deleted_rev = deleted_rev; details->deleted_rev_author = deleted_rev_author; - details->move = move; + details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath, + deleted_basename, + conflict->pool); + details->moves = moves; conflict->tree_conflict_local_details = details; @@ -1359,24 +1558,31 @@ describe_local_none_node_change(const ch return SVN_NO_ERROR; } -/* Append a description of all moves in the MOVE chain to DESCRIPTION. */ +/* Append a description of a move chain beginning at NEXT to DESCRIPTION. */ static const char * append_moved_to_chain_description(const char *description, - struct repos_move_info *move, + apr_array_header_t *next, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - if (move == NULL) + if (next == NULL) return description; - while (move) + while (next) { + struct repos_move_info *move; + + /* Describe the first possible move chain only. Adding multiple chains + * to the description would just be confusing. The user may select a + * different move destination while resolving the conflict. */ + move = APR_ARRAY_IDX(next, 0, struct repos_move_info *); + 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; + next = move->next; } return apr_pstrdup(result_pool, description); @@ -1386,6 +1592,7 @@ append_moved_to_chain_description(const static svn_error_t * conflict_tree_get_local_description_generic(const char **description, svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -1399,11 +1606,11 @@ conflict_tree_get_local_description_gene { case svn_node_file: case svn_node_symlink: - SVN_ERR(describe_local_file_node_change(description, conflict, + SVN_ERR(describe_local_file_node_change(description, conflict, ctx, result_pool, scratch_pool)); break; case svn_node_dir: - SVN_ERR(describe_local_dir_node_change(description, conflict, + SVN_ERR(describe_local_dir_node_change(description, conflict, ctx, result_pool, scratch_pool)); break; case svn_node_none: @@ -1420,6 +1627,7 @@ conflict_tree_get_local_description_gene static svn_error_t * conflict_tree_get_description_local_missing(const char **description, svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -1428,19 +1636,23 @@ conflict_tree_get_description_local_miss details = conflict->tree_conflict_local_details; if (details == NULL) return svn_error_trace(conflict_tree_get_local_description_generic( - description, conflict, result_pool, scratch_pool)); + description, conflict, ctx, + result_pool, scratch_pool)); - if (details->move) + if (details->moves) { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); *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); + move->moved_to_repos_relpath, + move->rev, move->rev_author); *description = append_moved_to_chain_description(*description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -1448,8 +1660,9 @@ conflict_tree_get_description_local_miss *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."), + "merge target working copy.\n'^/%s' was deleted " + "in r%ld by %s."), + details->deleted_repos_relpath, details->deleted_rev, details->deleted_rev_author); return SVN_NO_ERROR; @@ -1693,6 +1906,7 @@ static svn_error_t * conflict_tree_get_incoming_description_generic( const char **incoming_change_description, svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -1700,11 +1914,9 @@ conflict_tree_get_incoming_description_g svn_node_kind_t incoming_kind; svn_wc_conflict_action_t conflict_action; svn_wc_operation_t conflict_operation; - svn_node_kind_t conflict_node_kind; conflict_action = svn_client_conflict_get_incoming_change(conflict); conflict_operation = svn_client_conflict_get_operation(conflict); - conflict_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); /* Determine the node kind of the incoming change. */ incoming_kind = svn_node_unknown; @@ -1767,10 +1979,36 @@ 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; + /* Move information. 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 or in ADDED_REV (in which case moves should be interpreted + * in reverse). */ + apr_array_header_t *moves; + + /* A map of repos_relpaths and working copy nodes for an incoming move. + * + * Each key is a "const char *" repository relpath corresponding to a + * possible repository-side move destination node in the revision which + * is the target revision in case of update and switch, or 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 "follow" the incoming move 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; + + /* 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; }; static const char * @@ -1786,62 +2024,180 @@ describe_incoming_deletion_upon_update( details->replacing_node_kind == svn_node_symlink) { if (victim_node_kind == svn_node_dir) - return apr_psprintf(result_pool, - _("Directory updated from r%ld to r%ld was " - "replaced with a file by %s in r%ld."), - old_rev, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Directory updated from r%ld to r%ld was " + "replaced with a file by %s in r%ld."), + old_rev, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced directory was moved to " + "'^/%s'."), description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } 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 replaced " - "with a file from another line of history by " - "%s in r%ld."), - old_rev, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("File updated from r%ld to r%ld was replaced " + "with a file from another line of history by " + "%s in r%ld."), + old_rev, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced file was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } else - return apr_psprintf(result_pool, - _("Item updated from r%ld to r%ld was replaced " - "with a file by %s in r%ld."), old_rev, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Item updated from r%ld to r%ld was replaced " + "with a file by %s in r%ld."), old_rev, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced item was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } } else if (details->replacing_node_kind == svn_node_dir) { if (victim_node_kind == svn_node_dir) - return apr_psprintf(result_pool, - _("Directory updated from r%ld to r%ld was " - "replaced with a directory from another line " - "of history by %s in r%ld."), - old_rev, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Directory updated from r%ld to r%ld was " + "replaced with a directory from another line " + "of history by %s in r%ld."), + old_rev, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced directory was moved to " + "'^/%s'."), description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } else if (victim_node_kind == svn_node_file || victim_node_kind == svn_node_symlink) - return apr_psprintf(result_pool, - _("Directory updated from r%ld to r%ld was " - "replaced with a file by %s in r%ld."), - old_rev, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("File updated from r%ld to r%ld was " + "replaced with a directory by %s in r%ld."), + old_rev, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced file was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } else - return apr_psprintf(result_pool, - _("Item updated from r%ld to r%ld was replaced " - "by %s in r%ld."), old_rev, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Item updated from r%ld to r%ld was replaced " + "by %s in r%ld."), old_rev, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced item was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } } else { if (victim_node_kind == svn_node_dir) { - if (details->move) + if (details->moves) { - const char *description = + const char *description; + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + 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, + move->moved_to_repos_relpath, details->rev_author, details->deleted_rev); return append_moved_to_chain_description(description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -1855,16 +2211,20 @@ describe_incoming_deletion_upon_update( else if (victim_node_kind == svn_node_file || victim_node_kind == svn_node_symlink) { - if (details->move) + if (details->moves) { - const char *description = + struct repos_move_info *move; + const char *description; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + 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, + move->moved_to_repos_relpath, details->rev_author, details->deleted_rev); return append_moved_to_chain_description(description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -1876,16 +2236,20 @@ describe_incoming_deletion_upon_update( } else { - if (details->move) + if (details->moves) { - const char *description = + const char *description; + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + 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, + move->moved_to_repos_relpath, details->rev_author, details->deleted_rev); return append_moved_to_chain_description(description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -1992,78 +2356,196 @@ describe_incoming_deletion_upon_switch( details->replacing_node_kind == svn_node_symlink) { if (victim_node_kind == svn_node_dir) - return apr_psprintf(result_pool, - _("Directory switched from\n" - "'^/%s@%ld'\nto\n'^/%s@%ld'\n" - "was replaced with a file by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Directory switched from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\n" + "was replaced with a file by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced directory was moved " + "to '^/%s'."), description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } 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 " - "replaced with a file from another line of " - "history by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("File switched from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " + "replaced with a file from another line of " + "history by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced file was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } else - return apr_psprintf(result_pool, - _("Item switched from\n" - "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " - "replaced with a file by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Item switched from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " + "replaced with a file by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced item was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } } else if (details->replacing_node_kind == svn_node_dir) { if (victim_node_kind == svn_node_dir) - return apr_psprintf(result_pool, - _("Directory switched from\n" - "'^/%s@%ld'\nto\n'^/%s@%ld'\n" - "was replaced with a directory from another " - "line of history by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Directory switched from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\n" + "was replaced with a directory from another " + "line of history by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced directory was moved to " + "'^/%s'."), description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } 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'\n" - "was replaced with a directory by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("File switched from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\n" + "was replaced with a directory by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced file was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } else - return apr_psprintf(result_pool, - _("Item switched from\n" - "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " - "replaced with a directory by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Item switched from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " + "replaced with a directory by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced item was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } } else { if (victim_node_kind == svn_node_dir) { - if (details->move) + if (details->moves) { - const char *description = + struct repos_move_info *move; + const char *description; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + 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, + move->moved_to_repos_relpath, details->rev_author, details->deleted_rev); return append_moved_to_chain_description(description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -2079,19 +2561,23 @@ describe_incoming_deletion_upon_switch( else if (victim_node_kind == svn_node_file || victim_node_kind == svn_node_symlink) { - if (details->move) + if (details->moves) { - const char *description = + struct repos_move_info *move; + const char *description; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + 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, + move->moved_to_repos_relpath, details->rev_author, details->deleted_rev); return append_moved_to_chain_description(description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -2106,19 +2592,23 @@ describe_incoming_deletion_upon_switch( } else { - if (details->move) + if (details->moves) { - const char *description = + struct repos_move_info *move; + const char *description; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + 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, + move->moved_to_repos_relpath, details->rev_author, details->deleted_rev); return append_moved_to_chain_description(description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -2253,23 +2743,61 @@ describe_incoming_deletion_upon_merge( details->replacing_node_kind == svn_node_symlink) { if (victim_node_kind == svn_node_dir) - return apr_psprintf(result_pool, - _("Directory merged from\n" - "'^/%s@%ld'\nto\n'^/%s@%ld'\n" - "was replaced with a file by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Directory merged from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\n" + "was replaced with a file by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced directory was moved to " + "'^/%s'."), description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } 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 " - "replaced with a file from another line of " - "history by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("File merged from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " + "replaced with a file from another line of " + "history by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced file was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } else return apr_psprintf(result_pool, _("Item merged from\n" @@ -2282,49 +2810,110 @@ describe_incoming_deletion_upon_merge( else if (details->replacing_node_kind == svn_node_dir) { if (victim_node_kind == svn_node_dir) - return apr_psprintf(result_pool, - _("Directory merged from\n" - "'^/%s@%ld'\nto\n'^/%s@%ld'\n" - "was replaced with a directory from another " - "line of history by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Directory merged from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\n" + "was replaced with a directory from another " + "line of history by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced directory was moved to " + "'^/%s'."), description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } 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'\n" - "was replaced with a directory by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("File merged from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\n" + "was replaced with a directory by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced file was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } else - return apr_psprintf(result_pool, - _("Item merged from\n" - "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " - "replaced with a directory by %s in r%ld."), - old_repos_relpath, old_rev, - new_repos_relpath, new_rev, - details->rev_author, details->deleted_rev); + { + const char *description = + apr_psprintf(result_pool, + _("Item merged from\n" + "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " + "replaced with a directory by %s in r%ld."), + old_repos_relpath, old_rev, + new_repos_relpath, new_rev, + details->rev_author, details->deleted_rev); + if (details->moves) + { + struct repos_move_info *move; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + description = + apr_psprintf(result_pool, + _("%s\nThe replaced item was moved to '^/%s'."), + description, + move->moved_to_repos_relpath); + return append_moved_to_chain_description(description, + move->next, + result_pool, + scratch_pool); + } + return description; + } } else { if (victim_node_kind == svn_node_dir) { - if (details->move) + if (details->moves) { - const char *description = + struct repos_move_info *move; + const char *description; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + 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, + move->moved_to_repos_relpath, details->rev_author, details->deleted_rev); return append_moved_to_chain_description(description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -2340,19 +2929,23 @@ describe_incoming_deletion_upon_merge( else if (victim_node_kind == svn_node_file || victim_node_kind == svn_node_symlink) { - if (details->move) + if (details->moves) { - const char *description = + struct repos_move_info *move; + const char *description; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + 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, + move->moved_to_repos_relpath, details->rev_author, details->deleted_rev); return append_moved_to_chain_description(description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -2367,19 +2960,23 @@ describe_incoming_deletion_upon_merge( } else { - if (details->move) + if (details->moves) { - const char *description = + struct repos_move_info *move; + const char *description; + + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); + 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, + move->moved_to_repos_relpath, details->rev_author, details->deleted_rev); return append_moved_to_chain_description(description, - details->move->next, + move->next, result_pool, scratch_pool); } @@ -2501,6 +3098,7 @@ static svn_error_t * conflict_tree_get_description_incoming_delete( const char **incoming_change_description, svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -2516,7 +3114,7 @@ conflict_tree_get_description_incoming_d if (conflict->tree_conflict_incoming_details == NULL) return svn_error_trace(conflict_tree_get_incoming_description_generic( incoming_change_description, - conflict, result_pool, scratch_pool)); + conflict, ctx, result_pool, scratch_pool)); conflict_operation = svn_client_conflict_get_operation(conflict); victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); @@ -2599,6 +3197,8 @@ conflict_tree_get_description_incoming_d /* Baton for find_added_rev(). */ struct find_added_rev_baton { + const char *victim_abspath; + svn_client_ctx_t *ctx; svn_revnum_t added_rev; const char *repos_relpath; const char *parent_repos_relpath; @@ -2617,6 +3217,18 @@ find_added_rev(svn_location_segment_t *s { struct find_added_rev_baton *b = baton; + if (b->ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify( + b->victim_abspath, + svn_wc_notify_tree_conflict_details_progress, + scratch_pool), + notify->revision = segment->range_start; + b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool); + } + if (segment->path) /* not interested in gaps */ { if (b->parent_repos_relpath == NULL || @@ -2641,6 +3253,7 @@ get_incoming_delete_details_for_reverse_ svn_revnum_t old_rev, svn_revnum_t new_rev, svn_client_ctx_t *ctx, + const char *victim_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -2662,15 +3275,19 @@ get_incoming_delete_details_for_reverse_ scratch_pool)); *details = apr_pcalloc(result_pool, sizeof(**details)); + b.ctx = ctx; + b.victim_abspath = victim_abspath; 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, old_rev, new_rev, find_added_rev, &b, scratch_pool)); + SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev, SVN_PROP_REVISION_AUTHOR, &author_revprop, scratch_pool)); @@ -2700,6 +3317,7 @@ get_incoming_delete_details_for_reverse_ * Find the revision in which the victim was deleted in the repository. */ static svn_error_t * conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { const char *old_repos_relpath;
[... 4054 lines stripped ...]