1/* 2 * conflicts.c: conflict resolver implementation 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include "svn_types.h" 31#include "svn_wc.h" 32#include "svn_client.h" 33#include "svn_error.h" 34#include "svn_dirent_uri.h" 35#include "svn_path.h" 36#include "svn_pools.h" 37#include "svn_props.h" 38#include "svn_hash.h" 39#include "svn_sorts.h" 40#include "svn_subst.h" 41#include "client.h" 42 43#include "private/svn_diff_tree.h" 44#include "private/svn_ra_private.h" 45#include "private/svn_sorts_private.h" 46#include "private/svn_token.h" 47#include "private/svn_wc_private.h" 48 49#include "svn_private_config.h" 50 51#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0]))) 52 53 54/*** Dealing with conflicts. ***/ 55 56/* Describe a tree conflict. */ 57typedef svn_error_t *(*tree_conflict_get_description_func_t)( 58 const char **change_description, 59 svn_client_conflict_t *conflict, 60 svn_client_ctx_t *ctx, 61 apr_pool_t *result_pool, 62 apr_pool_t *scratch_pool); 63 64/* Get more information about a tree conflict. 65 * This function may contact the repository. */ 66typedef svn_error_t *(*tree_conflict_get_details_func_t)( 67 svn_client_conflict_t *conflict, 68 svn_client_ctx_t *ctx, 69 apr_pool_t *scratch_pool); 70 71struct svn_client_conflict_t 72{ 73 const char *local_abspath; 74 apr_hash_t *prop_conflicts; 75 76 /* Indicate which options were chosen to resolve a text or tree conflict 77 * on the conflicted node. */ 78 svn_client_conflict_option_id_t resolution_text; 79 svn_client_conflict_option_id_t resolution_tree; 80 81 /* A mapping from const char* property name to pointers to 82 * svn_client_conflict_option_t for all properties which had their 83 * conflicts resolved. Indicates which options were chosen to resolve 84 * the property conflicts. */ 85 apr_hash_t *resolved_props; 86 87 /* Ask a tree conflict to describe itself. */ 88 tree_conflict_get_description_func_t 89 tree_conflict_get_incoming_description_func; 90 tree_conflict_get_description_func_t 91 tree_conflict_get_local_description_func; 92 93 /* Ask a tree conflict to find out more information about itself 94 * by contacting the repository. */ 95 tree_conflict_get_details_func_t tree_conflict_get_incoming_details_func; 96 tree_conflict_get_details_func_t tree_conflict_get_local_details_func; 97 98 /* Any additional information found can be stored here and may be used 99 * when describing a tree conflict. */ 100 void *tree_conflict_incoming_details; 101 void *tree_conflict_local_details; 102 103 /* The pool this conflict was allocated from. */ 104 apr_pool_t *pool; 105 106 /* Conflict data provided by libsvn_wc. */ 107 const svn_wc_conflict_description2_t *legacy_text_conflict; 108 const char *legacy_prop_conflict_propname; 109 const svn_wc_conflict_description2_t *legacy_tree_conflict; 110 111 /* The recommended resolution option's ID. */ 112 svn_client_conflict_option_id_t recommended_option_id; 113}; 114 115/* Resolves conflict to OPTION and sets CONFLICT->RESOLUTION accordingly. 116 * 117 * May raise an error in case the conflict could not be resolved. A common 118 * case would be a tree conflict the resolution of which depends on other 119 * tree conflicts to be resolved first. */ 120typedef svn_error_t *(*conflict_option_resolve_func_t)( 121 svn_client_conflict_option_t *option, 122 svn_client_conflict_t *conflict, 123 svn_client_ctx_t *ctx, 124 apr_pool_t *scratch_pool); 125 126struct svn_client_conflict_option_t 127{ 128 svn_client_conflict_option_id_t id; 129 const char *label; 130 const char *description; 131 132 svn_client_conflict_t *conflict; 133 conflict_option_resolve_func_t do_resolve_func; 134 135 /* The pool this option was allocated from. */ 136 apr_pool_t *pool; 137 138 /* Data which is specific to particular conflicts and options. */ 139 union { 140 struct { 141 /* Indicates the property to resolve in case of a property conflict. 142 * If set to "", all properties are resolved to this option. */ 143 const char *propname; 144 145 /* A merged property value, if supplied by the API user, else NULL. */ 146 const svn_string_t *merged_propval; 147 } prop; 148 } type_data; 149 150}; 151 152/* 153 * Return a legacy conflict choice corresponding to OPTION_ID. 154 * Return svn_wc_conflict_choose_undefined if no corresponding 155 * legacy conflict choice exists. 156 */ 157static svn_wc_conflict_choice_t 158conflict_option_id_to_wc_conflict_choice( 159 svn_client_conflict_option_id_t option_id) 160{ 161 162 switch (option_id) 163 { 164 case svn_client_conflict_option_undefined: 165 return svn_wc_conflict_choose_undefined; 166 167 case svn_client_conflict_option_postpone: 168 return svn_wc_conflict_choose_postpone; 169 170 case svn_client_conflict_option_base_text: 171 return svn_wc_conflict_choose_base; 172 173 case svn_client_conflict_option_incoming_text: 174 return svn_wc_conflict_choose_theirs_full; 175 176 case svn_client_conflict_option_working_text: 177 return svn_wc_conflict_choose_mine_full; 178 179 case svn_client_conflict_option_incoming_text_where_conflicted: 180 return svn_wc_conflict_choose_theirs_conflict; 181 182 case svn_client_conflict_option_working_text_where_conflicted: 183 return svn_wc_conflict_choose_mine_conflict; 184 185 case svn_client_conflict_option_merged_text: 186 return svn_wc_conflict_choose_merged; 187 188 case svn_client_conflict_option_unspecified: 189 return svn_wc_conflict_choose_unspecified; 190 191 default: 192 break; 193 } 194 195 return svn_wc_conflict_choose_undefined; 196} 197 198static void 199add_legacy_desc_to_conflict(const svn_wc_conflict_description2_t *desc, 200 svn_client_conflict_t *conflict, 201 apr_pool_t *result_pool) 202{ 203 switch (desc->kind) 204 { 205 case svn_wc_conflict_kind_text: 206 conflict->legacy_text_conflict = desc; 207 break; 208 209 case svn_wc_conflict_kind_property: 210 if (conflict->prop_conflicts == NULL) 211 conflict->prop_conflicts = apr_hash_make(result_pool); 212 svn_hash_sets(conflict->prop_conflicts, desc->property_name, desc); 213 conflict->legacy_prop_conflict_propname = desc->property_name; 214 break; 215 216 case svn_wc_conflict_kind_tree: 217 conflict->legacy_tree_conflict = desc; 218 break; 219 220 default: 221 SVN_ERR_ASSERT_NO_RETURN(FALSE); /* unknown kind of conflict */ 222 } 223} 224 225/* A map for svn_wc_conflict_action_t values to strings */ 226static const svn_token_map_t map_conflict_action[] = 227{ 228 { "edit", svn_wc_conflict_action_edit }, 229 { "delete", svn_wc_conflict_action_delete }, 230 { "add", svn_wc_conflict_action_add }, 231 { "replace", svn_wc_conflict_action_replace }, 232 { NULL, 0 } 233}; 234 235/* A map for svn_wc_conflict_reason_t values to strings */ 236static const svn_token_map_t map_conflict_reason[] = 237{ 238 { "edit", svn_wc_conflict_reason_edited }, 239 { "delete", svn_wc_conflict_reason_deleted }, 240 { "missing", svn_wc_conflict_reason_missing }, 241 { "obstruction", svn_wc_conflict_reason_obstructed }, 242 { "add", svn_wc_conflict_reason_added }, 243 { "replace", svn_wc_conflict_reason_replaced }, 244 { "unversioned", svn_wc_conflict_reason_unversioned }, 245 { "moved-away", svn_wc_conflict_reason_moved_away }, 246 { "moved-here", svn_wc_conflict_reason_moved_here }, 247 { NULL, 0 } 248}; 249 250/* Describes a server-side move (really a copy+delete within the same 251 * revision) which was identified by scanning the revision log. 252 * This structure can represent one or more "chains" of moves, i.e. 253 * multiple move operations which occurred across a range of revisions. */ 254struct repos_move_info { 255 /* The revision in which this move was committed. */ 256 svn_revnum_t rev; 257 258 /* The author who committed the revision in which this move was committed. */ 259 const char *rev_author; 260 261 /* The repository relpath the node was moved from in this revision. */ 262 const char *moved_from_repos_relpath; 263 264 /* The repository relpath the node was moved to in this revision. */ 265 const char *moved_to_repos_relpath; 266 267 /* The copyfrom revision of the moved-to path. */ 268 svn_revnum_t copyfrom_rev; 269 270 /* The node kind of the item being moved. */ 271 svn_node_kind_t node_kind; 272 273 /* Prev pointer. NULL if no prior move exists in the chain. */ 274 struct repos_move_info *prev; 275 276 /* An array of struct repos_move_info * elements, each representing 277 * a possible way forward in the move chain. NULL if no next move 278 * exists in this chain. If the deleted node was copied only once in 279 * this revision, then this array has only one element and the move 280 * chain does not fork. But if this revision contains multiple copies of 281 * the deleted node, each of these copies appears as an element of this 282 * array, and each element represents a different path the next move 283 * might have taken. */ 284 apr_array_header_t *next; 285}; 286 287static svn_revnum_t 288rev_below(svn_revnum_t rev) 289{ 290 SVN_ERR_ASSERT_NO_RETURN(rev != SVN_INVALID_REVNUM); 291 SVN_ERR_ASSERT_NO_RETURN(rev > 0); 292 293 return rev == 1 ? 1 : rev - 1; 294} 295 296/* Set *RELATED to true if the deleted node DELETED_REPOS_RELPATH@DELETED_REV 297 * is an ancestor of the copied node COPYFROM_PATH@COPYFROM_REV. 298 * If CHECK_LAST_CHANGED_REV is non-zero, also ensure that the copied node 299 * is a copy of the deleted node's last-changed revision's content, rather 300 * than a copy of some older content. If it's not, set *RELATED to false. */ 301static svn_error_t * 302check_move_ancestry(svn_boolean_t *related, 303 svn_ra_session_t *ra_session, 304 const char *repos_root_url, 305 const char *deleted_repos_relpath, 306 svn_revnum_t deleted_rev, 307 const char *copyfrom_path, 308 svn_revnum_t copyfrom_rev, 309 svn_boolean_t check_last_changed_rev, 310 apr_pool_t *scratch_pool) 311{ 312 apr_hash_t *locations; 313 const char *deleted_url; 314 const char *deleted_location; 315 apr_array_header_t *location_revisions; 316 const char *old_session_url; 317 318 location_revisions = apr_array_make(scratch_pool, 1, sizeof(svn_revnum_t)); 319 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = copyfrom_rev; 320 deleted_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, 321 repos_root_url, "/", 322 deleted_repos_relpath, 323 NULL), 324 scratch_pool); 325 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, 326 deleted_url, scratch_pool)); 327 SVN_ERR(svn_ra_get_locations(ra_session, &locations, "", 328 rev_below(deleted_rev), location_revisions, 329 scratch_pool)); 330 331 deleted_location = apr_hash_get(locations, ©from_rev, 332 sizeof(svn_revnum_t)); 333 if (deleted_location) 334 { 335 if (deleted_location[0] == '/') 336 deleted_location++; 337 if (strcmp(deleted_location, copyfrom_path) != 0) 338 { 339 *related = FALSE; 340 return SVN_NO_ERROR; 341 } 342 } 343 else 344 { 345 *related = FALSE; 346 return SVN_NO_ERROR; 347 } 348 349 if (check_last_changed_rev) 350 { 351 svn_dirent_t *dirent; 352 353 /* Verify that copyfrom_rev >= last-changed revision of the 354 * deleted node. */ 355 SVN_ERR(svn_ra_stat(ra_session, "", rev_below(deleted_rev), &dirent, 356 scratch_pool)); 357 if (dirent == NULL || copyfrom_rev < dirent->created_rev) 358 { 359 *related = FALSE; 360 return SVN_NO_ERROR; 361 } 362 } 363 364 *related = TRUE; 365 return SVN_NO_ERROR; 366} 367 368struct copy_info { 369 const char *copyto_path; 370 const char *copyfrom_path; 371 svn_revnum_t copyfrom_rev; 372 svn_node_kind_t node_kind; 373}; 374 375/* Allocate and return a NEW_MOVE, and update MOVED_PATHS with this new move. */ 376static svn_error_t * 377add_new_move(struct repos_move_info **new_move, 378 const char *deleted_repos_relpath, 379 const char *copyto_path, 380 svn_revnum_t copyfrom_rev, 381 svn_node_kind_t node_kind, 382 svn_revnum_t revision, 383 const char *author, 384 apr_hash_t *moved_paths, 385 svn_ra_session_t *ra_session, 386 const char *repos_root_url, 387 apr_pool_t *result_pool, 388 apr_pool_t *scratch_pool) 389{ 390 struct repos_move_info *move; 391 struct repos_move_info *next_move; 392 393 move = apr_pcalloc(result_pool, sizeof(*move)); 394 move->moved_from_repos_relpath = apr_pstrdup(result_pool, 395 deleted_repos_relpath); 396 move->moved_to_repos_relpath = apr_pstrdup(result_pool, copyto_path); 397 move->rev = revision; 398 move->rev_author = apr_pstrdup(result_pool, author); 399 move->copyfrom_rev = copyfrom_rev; 400 move->node_kind = node_kind; 401 402 /* Link together multiple moves of the same node. 403 * Note that we're traversing history backwards, so moves already 404 * present in the list happened in younger revisions. */ 405 next_move = svn_hash_gets(moved_paths, move->moved_to_repos_relpath); 406 if (next_move) 407 { 408 svn_boolean_t related; 409 410 /* Tracing back history of the delete-half of the next move 411 * to the copyfrom-revision of the prior move we must end up 412 * at the delete-half of the prior move. */ 413 SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url, 414 next_move->moved_from_repos_relpath, 415 next_move->rev, 416 move->moved_from_repos_relpath, 417 move->copyfrom_rev, 418 FALSE, scratch_pool)); 419 if (related) 420 { 421 SVN_ERR_ASSERT(move->rev < next_move->rev); 422 423 /* Prepend this move to the linked list. */ 424 if (move->next == NULL) 425 move->next = apr_array_make(result_pool, 1, 426 sizeof (struct repos_move_info *)); 427 APR_ARRAY_PUSH(move->next, struct repos_move_info *) = next_move; 428 next_move->prev = move; 429 } 430 } 431 432 /* Make this move the head of our next-move linking map. */ 433 svn_hash_sets(moved_paths, move->moved_from_repos_relpath, move); 434 435 *new_move = move; 436 return SVN_NO_ERROR; 437} 438 439/* Push a MOVE into the MOVES_TABLE. */ 440static void 441push_move(struct repos_move_info *move, apr_hash_t *moves_table, 442 apr_pool_t *result_pool) 443{ 444 apr_array_header_t *moves; 445 446 /* Add this move to the list of moves in the revision. */ 447 moves = apr_hash_get(moves_table, &move->rev, sizeof(svn_revnum_t)); 448 if (moves == NULL) 449 { 450 /* It is the first move in this revision. Create the list. */ 451 moves = apr_array_make(result_pool, 1, sizeof(struct repos_move_info *)); 452 apr_hash_set(moves_table, &move->rev, sizeof(svn_revnum_t), moves); 453 } 454 APR_ARRAY_PUSH(moves, struct repos_move_info *) = move; 455} 456 457/* Find the youngest common ancestor of REPOS_RELPATH1@PEG_REV1 and 458 * REPOS_RELPATH2@PEG_REV2. Return the result in *YCA_LOC. 459 * Set *YCA_LOC to NULL if no common ancestor exists. */ 460static svn_error_t * 461find_yca(svn_client__pathrev_t **yca_loc, 462 const char *repos_relpath1, 463 svn_revnum_t peg_rev1, 464 const char *repos_relpath2, 465 svn_revnum_t peg_rev2, 466 const char *repos_root_url, 467 const char *repos_uuid, 468 svn_ra_session_t *ra_session, 469 svn_client_ctx_t *ctx, 470 apr_pool_t *result_pool, 471 apr_pool_t *scratch_pool) 472{ 473 svn_client__pathrev_t *loc1; 474 svn_client__pathrev_t *loc2; 475 476 *yca_loc = NULL; 477 478 loc1 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid, 479 peg_rev1, repos_relpath1, 480 scratch_pool); 481 loc2 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid, 482 peg_rev2, repos_relpath2, 483 scratch_pool); 484 SVN_ERR(svn_client__get_youngest_common_ancestor(yca_loc, loc1, loc2, 485 ra_session, ctx, 486 result_pool, scratch_pool)); 487 488 return SVN_NO_ERROR; 489} 490 491/* Like find_yca, expect that a YCA could also be found via a brute-force 492 * search of parents of REPOS_RELPATH1 and REPOS_RELPATH2, if no "direct" 493 * YCA exists. An implicit assumption is that some parent of REPOS_RELPATH1 494 * is a branch of some parent of REPOS_RELPATH2. 495 * 496 * This function can guess a "good enough" YCA for 'missing nodes' which do 497 * not exist in the working copy, e.g. when a file edit is merged to a path 498 * which does not exist in the working copy. 499 */ 500static svn_error_t * 501find_nearest_yca(svn_client__pathrev_t **yca_locp, 502 const char *repos_relpath1, 503 svn_revnum_t peg_rev1, 504 const char *repos_relpath2, 505 svn_revnum_t peg_rev2, 506 const char *repos_root_url, 507 const char *repos_uuid, 508 svn_ra_session_t *ra_session, 509 svn_client_ctx_t *ctx, 510 apr_pool_t *result_pool, 511 apr_pool_t *scratch_pool) 512{ 513 svn_client__pathrev_t *yca_loc; 514 svn_error_t *err; 515 apr_pool_t *iterpool; 516 const char *p1, *p2; 517 apr_size_t c1, c2; 518 519 *yca_locp = NULL; 520 521 iterpool = svn_pool_create(scratch_pool); 522 523 p1 = repos_relpath1; 524 c1 = svn_path_component_count(repos_relpath1); 525 while (c1--) 526 { 527 svn_pool_clear(iterpool); 528 529 p2 = repos_relpath2; 530 c2 = svn_path_component_count(repos_relpath2); 531 while (c2--) 532 { 533 err = find_yca(&yca_loc, p1, peg_rev1, p2, peg_rev2, 534 repos_root_url, repos_uuid, ra_session, ctx, 535 result_pool, iterpool); 536 if (err) 537 { 538 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 539 { 540 svn_error_clear(err); 541 yca_loc = NULL; 542 } 543 else 544 return svn_error_trace(err); 545 } 546 547 if (yca_loc) 548 { 549 *yca_locp = yca_loc; 550 svn_pool_destroy(iterpool); 551 return SVN_NO_ERROR; 552 } 553 554 p2 = svn_relpath_dirname(p2, scratch_pool); 555 } 556 557 p1 = svn_relpath_dirname(p1, scratch_pool); 558 } 559 560 svn_pool_destroy(iterpool); 561 562 return SVN_NO_ERROR; 563} 564 565/* Check if the copied node described by COPY and the DELETED_PATH@DELETED_REV 566 * share a common ancestor. If so, return new repos_move_info in *MOVE which 567 * describes a move from the deleted path to that copy's destination. */ 568static svn_error_t * 569find_related_move(struct repos_move_info **move, 570 struct copy_info *copy, 571 const char *deleted_repos_relpath, 572 svn_revnum_t deleted_rev, 573 const char *author, 574 apr_hash_t *moved_paths, 575 const char *repos_root_url, 576 const char *repos_uuid, 577 svn_client_ctx_t *ctx, 578 svn_ra_session_t *ra_session, 579 apr_pool_t *result_pool, 580 apr_pool_t *scratch_pool) 581{ 582 svn_client__pathrev_t *yca_loc; 583 svn_error_t *err; 584 585 *move = NULL; 586 err = find_yca(&yca_loc, copy->copyfrom_path, copy->copyfrom_rev, 587 deleted_repos_relpath, rev_below(deleted_rev), 588 repos_root_url, repos_uuid, ra_session, ctx, 589 scratch_pool, scratch_pool); 590 if (err) 591 { 592 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 593 { 594 svn_error_clear(err); 595 yca_loc = NULL; 596 } 597 else 598 return svn_error_trace(err); 599 } 600 601 if (yca_loc) 602 SVN_ERR(add_new_move(move, deleted_repos_relpath, 603 copy->copyto_path, copy->copyfrom_rev, 604 copy->node_kind, deleted_rev, author, 605 moved_paths, ra_session, repos_root_url, 606 result_pool, scratch_pool)); 607 608 return SVN_NO_ERROR; 609} 610 611/* Detect moves by matching DELETED_REPOS_RELPATH@DELETED_REV to the copies 612 * in COPIES. Add any moves found to MOVES_TABLE and update MOVED_PATHS. */ 613static svn_error_t * 614match_copies_to_deletion(const char *deleted_repos_relpath, 615 svn_revnum_t deleted_rev, 616 const char *author, 617 apr_hash_t *copies, 618 apr_hash_t *moves_table, 619 apr_hash_t *moved_paths, 620 const char *repos_root_url, 621 const char *repos_uuid, 622 svn_ra_session_t *ra_session, 623 svn_client_ctx_t *ctx, 624 apr_pool_t *result_pool, 625 apr_pool_t *scratch_pool) 626{ 627 apr_hash_index_t *hi; 628 apr_pool_t *iterpool; 629 630 iterpool = svn_pool_create(scratch_pool); 631 for (hi = apr_hash_first(scratch_pool, copies); 632 hi != NULL; 633 hi = apr_hash_next(hi)) 634 { 635 const char *copyfrom_path = apr_hash_this_key(hi); 636 apr_array_header_t *copies_with_same_source_path; 637 int i; 638 639 svn_pool_clear(iterpool); 640 641 copies_with_same_source_path = apr_hash_this_val(hi); 642 643 if (strcmp(copyfrom_path, deleted_repos_relpath) == 0) 644 { 645 /* We found a copyfrom path which matches a deleted node. 646 * Check if the deleted node is an ancestor of the copied node. */ 647 for (i = 0; i < copies_with_same_source_path->nelts; i++) 648 { 649 struct copy_info *copy; 650 svn_boolean_t related; 651 struct repos_move_info *move; 652 653 copy = APR_ARRAY_IDX(copies_with_same_source_path, i, 654 struct copy_info *); 655 SVN_ERR(check_move_ancestry(&related, 656 ra_session, repos_root_url, 657 deleted_repos_relpath, 658 deleted_rev, 659 copy->copyfrom_path, 660 copy->copyfrom_rev, 661 TRUE, iterpool)); 662 if (!related) 663 continue; 664 665 /* Remember details of this move. */ 666 SVN_ERR(add_new_move(&move, deleted_repos_relpath, 667 copy->copyto_path, copy->copyfrom_rev, 668 copy->node_kind, deleted_rev, author, 669 moved_paths, ra_session, repos_root_url, 670 result_pool, iterpool)); 671 push_move(move, moves_table, result_pool); 672 } 673 } 674 else 675 { 676 /* Check if this deleted node is related to any copies in this 677 * revision. These could be moves of the deleted node which 678 * were merged here from other lines of history. */ 679 for (i = 0; i < copies_with_same_source_path->nelts; i++) 680 { 681 struct copy_info *copy; 682 struct repos_move_info *move = NULL; 683 684 copy = APR_ARRAY_IDX(copies_with_same_source_path, i, 685 struct copy_info *); 686 SVN_ERR(find_related_move(&move, copy, deleted_repos_relpath, 687 deleted_rev, author, 688 moved_paths, 689 repos_root_url, repos_uuid, 690 ctx, ra_session, 691 result_pool, iterpool)); 692 if (move) 693 push_move(move, moves_table, result_pool); 694 } 695 } 696 } 697 svn_pool_destroy(iterpool); 698 699 return SVN_NO_ERROR; 700} 701 702/* Update MOVES_TABLE and MOVED_PATHS based on information from 703 * revision data in LOG_ENTRY, COPIES, and DELETED_PATHS. 704 * Use RA_SESSION to perform the necessary requests. */ 705static svn_error_t * 706find_moves_in_revision(svn_ra_session_t *ra_session, 707 apr_hash_t *moves_table, 708 apr_hash_t *moved_paths, 709 svn_log_entry_t *log_entry, 710 apr_hash_t *copies, 711 apr_array_header_t *deleted_paths, 712 const char *repos_root_url, 713 const char *repos_uuid, 714 svn_client_ctx_t *ctx, 715 apr_pool_t *result_pool, 716 apr_pool_t *scratch_pool) 717{ 718 apr_pool_t *iterpool; 719 int i; 720 const svn_string_t *author; 721 722 author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR); 723 iterpool = svn_pool_create(scratch_pool); 724 for (i = 0; i < deleted_paths->nelts; i++) 725 { 726 const char *deleted_repos_relpath; 727 728 svn_pool_clear(iterpool); 729 730 deleted_repos_relpath = APR_ARRAY_IDX(deleted_paths, i, const char *); 731 SVN_ERR(match_copies_to_deletion(deleted_repos_relpath, 732 log_entry->revision, 733 author ? author->data 734 : _("unknown author"), 735 copies, moves_table, moved_paths, 736 repos_root_url, repos_uuid, ra_session, 737 ctx, result_pool, iterpool)); 738 } 739 svn_pool_destroy(iterpool); 740 741 return SVN_NO_ERROR; 742} 743 744struct find_deleted_rev_baton 745{ 746 /* Variables below are arguments provided by the caller of 747 * svn_ra_get_log2(). */ 748 const char *deleted_repos_relpath; 749 const char *related_repos_relpath; 750 svn_revnum_t related_peg_rev; 751 const char *repos_root_url; 752 const char *repos_uuid; 753 svn_client_ctx_t *ctx; 754 const char *victim_abspath; /* for notifications */ 755 756 /* Variables below are results for the caller of svn_ra_get_log2(). */ 757 svn_revnum_t deleted_rev; 758 const char *deleted_rev_author; 759 svn_node_kind_t replacing_node_kind; 760 apr_pool_t *result_pool; 761 762 apr_hash_t *moves_table; /* Obtained from find_moves_in_revision(). */ 763 struct repos_move_info *move; /* Last known move which affected the node. */ 764 765 /* Extra RA session that can be used to make additional requests. */ 766 svn_ra_session_t *extra_ra_session; 767}; 768 769/* If DELETED_RELPATH matches the moved-from path of a move in MOVES, 770 * or if DELETED_RELPATH is a child of a moved-to path in MOVES, return 771 * a struct move_info for the corresponding move. Else, return NULL. */ 772static struct repos_move_info * 773map_deleted_path_to_move(const char *deleted_relpath, 774 apr_array_header_t *moves, 775 apr_pool_t *scratch_pool) 776{ 777 struct repos_move_info *closest_move = NULL; 778 apr_size_t min_components = 0; 779 int i; 780 781 for (i = 0; i < moves->nelts; i++) 782 { 783 const char *relpath; 784 struct repos_move_info *move; 785 786 move = APR_ARRAY_IDX(moves, i, struct repos_move_info *); 787 if (strcmp(move->moved_from_repos_relpath, deleted_relpath) == 0) 788 return move; 789 790 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath, 791 deleted_relpath); 792 if (relpath) 793 { 794 /* This could be a nested move. Return the path-wise closest move. */ 795 const apr_size_t c = svn_path_component_count(relpath); 796 if (c == 0) 797 return move; 798 else if (min_components == 0 || c < min_components) 799 { 800 min_components = c; 801 closest_move = move; 802 } 803 } 804 } 805 806 if (closest_move) 807 { 808 const char *relpath; 809 810 /* See if we can find an even closer move for this moved-along path. */ 811 relpath = svn_relpath_skip_ancestor(closest_move->moved_to_repos_relpath, 812 deleted_relpath); 813 if (relpath && relpath[0] != '\0') 814 { 815 struct repos_move_info *move; 816 const char *moved_along_path = 817 svn_relpath_join(closest_move->moved_from_repos_relpath, relpath, 818 scratch_pool); 819 move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool); 820 if (move) 821 return move; 822 } 823 } 824 825 return closest_move; 826} 827 828/* Search for nested moves in REVISION, given the already found MOVES, 829 * all DELETED_PATHS, and all COPIES, from the same revision. 830 * Append any nested moves to the MOVES array. */ 831static svn_error_t * 832find_nested_moves(apr_array_header_t *moves, 833 apr_hash_t *copies, 834 apr_array_header_t *deleted_paths, 835 apr_hash_t *moved_paths, 836 svn_revnum_t revision, 837 const char *author, 838 const char *repos_root_url, 839 const char *repos_uuid, 840 svn_ra_session_t *ra_session, 841 svn_client_ctx_t *ctx, 842 apr_pool_t *result_pool, 843 apr_pool_t *scratch_pool) 844{ 845 apr_array_header_t *nested_moves; 846 int i; 847 apr_pool_t *iterpool; 848 849 nested_moves = apr_array_make(result_pool, 0, 850 sizeof(struct repos_move_info *)); 851 iterpool = svn_pool_create(scratch_pool); 852 for (i = 0; i < deleted_paths->nelts; i++) 853 { 854 const char *deleted_path; 855 const char *child_relpath; 856 const char *moved_along_repos_relpath; 857 struct repos_move_info *move; 858 apr_array_header_t *copies_with_same_source_path; 859 int j; 860 svn_boolean_t related; 861 862 svn_pool_clear(iterpool); 863 864 deleted_path = APR_ARRAY_IDX(deleted_paths, i, const char *); 865 move = map_deleted_path_to_move(deleted_path, moves, iterpool); 866 if (move == NULL) 867 continue; 868 child_relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath, 869 deleted_path); 870 if (child_relpath == NULL || child_relpath[0] == '\0') 871 continue; /* not a nested move */ 872 873 /* Consider: svn mv A B; svn mv B/foo C/foo 874 * Copyfrom for C/foo is A/foo, even though C/foo was moved here from 875 * B/foo. A/foo was not deleted. It is B/foo which was deleted. 876 * We now know about the move A->B and moved-along child_relpath "foo". 877 * Try to detect an ancestral relationship between A/foo and the 878 * moved-along path. */ 879 moved_along_repos_relpath = 880 svn_relpath_join(move->moved_from_repos_relpath, child_relpath, 881 iterpool); 882 copies_with_same_source_path = svn_hash_gets(copies, 883 moved_along_repos_relpath); 884 if (copies_with_same_source_path == NULL) 885 continue; /* not a nested move */ 886 887 for (j = 0; j < copies_with_same_source_path->nelts; j++) 888 { 889 struct copy_info *copy; 890 891 copy = APR_ARRAY_IDX(copies_with_same_source_path, j, 892 struct copy_info *); 893 SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url, 894 moved_along_repos_relpath, 895 revision, 896 copy->copyfrom_path, 897 copy->copyfrom_rev, 898 TRUE, iterpool)); 899 if (related) 900 { 901 struct repos_move_info *nested_move; 902 903 /* Remember details of this move. */ 904 SVN_ERR(add_new_move(&nested_move, moved_along_repos_relpath, 905 copy->copyto_path, copy->copyfrom_rev, 906 copy->node_kind, 907 revision, author, moved_paths, 908 ra_session, repos_root_url, 909 result_pool, iterpool)); 910 911 /* Add this move to the list of nested moves in this revision. */ 912 APR_ARRAY_PUSH(nested_moves, struct repos_move_info *) = 913 nested_move; 914 } 915 } 916 } 917 svn_pool_destroy(iterpool); 918 919 /* Add all nested moves found to the list of all moves in this revision. */ 920 apr_array_cat(moves, nested_moves); 921 922 return SVN_NO_ERROR; 923} 924 925/* Make a shallow copy of the copied LOG_ITEM in COPIES. */ 926static void 927cache_copied_item(apr_hash_t *copies, const char *changed_path, 928 svn_log_changed_path2_t *log_item) 929{ 930 apr_pool_t *result_pool = apr_hash_pool_get(copies); 931 struct copy_info *copy = apr_palloc(result_pool, sizeof(*copy)); 932 apr_array_header_t *copies_with_same_source_path; 933 934 copy->copyfrom_path = log_item->copyfrom_path; 935 if (log_item->copyfrom_path[0] == '/') 936 copy->copyfrom_path++; 937 copy->copyto_path = changed_path; 938 copy->copyfrom_rev = log_item->copyfrom_rev; 939 copy->node_kind = log_item->node_kind; 940 941 copies_with_same_source_path = apr_hash_get(copies, copy->copyfrom_path, 942 APR_HASH_KEY_STRING); 943 if (copies_with_same_source_path == NULL) 944 { 945 copies_with_same_source_path = apr_array_make(result_pool, 1, 946 sizeof(struct copy_info *)); 947 apr_hash_set(copies, copy->copyfrom_path, APR_HASH_KEY_STRING, 948 copies_with_same_source_path); 949 } 950 APR_ARRAY_PUSH(copies_with_same_source_path, struct copy_info *) = copy; 951} 952 953/* Implements svn_log_entry_receiver_t. 954 * 955 * Find the revision in which a node, optionally ancestrally related to the 956 * node specified via find_deleted_rev_baton, was deleted, When the revision 957 * was found, store it in BATON->DELETED_REV and abort the log operation 958 * by raising SVN_ERR_CEASE_INVOCATION. 959 * 960 * If no such revision can be found, leave BATON->DELETED_REV and 961 * BATON->REPLACING_NODE_KIND alone. 962 * 963 * If the node was replaced, set BATON->REPLACING_NODE_KIND to the node 964 * kind of the node which replaced the original node. If the node was not 965 * replaced, set BATON->REPLACING_NODE_KIND to svn_node_none. 966 * 967 * This function answers the same question as svn_ra_get_deleted_rev() but 968 * works in cases where we do not already know a revision in which the deleted 969 * node once used to exist. 970 * 971 * If the node was moved, rather than deleted, return move information 972 * in BATON->MOVE. 973 */ 974static svn_error_t * 975find_deleted_rev(void *baton, 976 svn_log_entry_t *log_entry, 977 apr_pool_t *scratch_pool) 978{ 979 struct find_deleted_rev_baton *b = baton; 980 apr_hash_index_t *hi; 981 apr_pool_t *iterpool; 982 svn_boolean_t deleted_node_found = FALSE; 983 svn_node_kind_t replacing_node_kind = svn_node_none; 984 985 if (b->ctx->notify_func2) 986 { 987 svn_wc_notify_t *notify; 988 989 notify = svn_wc_create_notify( 990 b->victim_abspath, 991 svn_wc_notify_tree_conflict_details_progress, 992 scratch_pool), 993 notify->revision = log_entry->revision; 994 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool); 995 } 996 997 /* No paths were changed in this revision. Nothing to do. */ 998 if (! log_entry->changed_paths2) 999 return SVN_NO_ERROR; 1000 1001 iterpool = svn_pool_create(scratch_pool); 1002 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2); 1003 hi != NULL; 1004 hi = apr_hash_next(hi)) 1005 { 1006 const char *changed_path = apr_hash_this_key(hi); 1007 svn_log_changed_path2_t *log_item = apr_hash_this_val(hi); 1008 1009 svn_pool_clear(iterpool); 1010 1011 /* ### Remove leading slash from paths in log entries. */ 1012 if (changed_path[0] == '/') 1013 changed_path++; 1014 1015 /* Check if we already found the deleted node we're looking for. */ 1016 if (!deleted_node_found && 1017 svn_path_compare_paths(b->deleted_repos_relpath, changed_path) == 0 && 1018 (log_item->action == 'D' || log_item->action == 'R')) 1019 { 1020 deleted_node_found = TRUE; 1021 1022 if (b->related_repos_relpath != NULL && 1023 b->related_peg_rev != SVN_INVALID_REVNUM) 1024 { 1025 svn_client__pathrev_t *yca_loc; 1026 svn_error_t *err; 1027 1028 /* We found a deleted node which occupies the correct path. 1029 * To be certain that this is the deleted node we're looking for, 1030 * we must establish whether it is ancestrally related to the 1031 * "related node" specified in our baton. */ 1032 err = find_yca(&yca_loc, 1033 b->related_repos_relpath, 1034 b->related_peg_rev, 1035 b->deleted_repos_relpath, 1036 rev_below(log_entry->revision), 1037 b->repos_root_url, b->repos_uuid, 1038 b->extra_ra_session, b->ctx, iterpool, iterpool); 1039 if (err) 1040 { 1041 /* ### Happens for moves within other moves and copies. */ 1042 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 1043 { 1044 svn_error_clear(err); 1045 yca_loc = NULL; 1046 } 1047 else 1048 return svn_error_trace(err); 1049 } 1050 1051 deleted_node_found = (yca_loc != NULL); 1052 } 1053 1054 if (deleted_node_found && log_item->action == 'R') 1055 replacing_node_kind = log_item->node_kind; 1056 } 1057 } 1058 svn_pool_destroy(iterpool); 1059 1060 if (!deleted_node_found) 1061 { 1062 apr_array_header_t *moves; 1063 1064 if (b->moves_table == NULL) 1065 return SVN_NO_ERROR; 1066 1067 moves = apr_hash_get(b->moves_table, &log_entry->revision, 1068 sizeof(svn_revnum_t)); 1069 if (moves) 1070 { 1071 struct repos_move_info *move; 1072 1073 move = map_deleted_path_to_move(b->deleted_repos_relpath, 1074 moves, scratch_pool); 1075 if (move) 1076 { 1077 const char *relpath; 1078 1079 /* The node was moved. Update our search path accordingly. */ 1080 b->move = move; 1081 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath, 1082 b->deleted_repos_relpath); 1083 if (relpath) 1084 b->deleted_repos_relpath = 1085 svn_relpath_join(move->moved_from_repos_relpath, relpath, 1086 b->result_pool); 1087 } 1088 } 1089 } 1090 else 1091 { 1092 svn_string_t *author; 1093 1094 b->deleted_rev = log_entry->revision; 1095 author = svn_hash_gets(log_entry->revprops, 1096 SVN_PROP_REVISION_AUTHOR); 1097 if (author) 1098 b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data); 1099 else 1100 b->deleted_rev_author = _("unknown author"); 1101 1102 b->replacing_node_kind = replacing_node_kind; 1103 1104 /* We're done. Abort the log operation. */ 1105 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); 1106 } 1107 1108 return SVN_NO_ERROR; 1109} 1110 1111/* Return a localised string representation of the local part of a tree 1112 conflict on a file. */ 1113static svn_error_t * 1114describe_local_file_node_change(const char **description, 1115 svn_client_conflict_t *conflict, 1116 svn_client_ctx_t *ctx, 1117 apr_pool_t *result_pool, 1118 apr_pool_t *scratch_pool) 1119{ 1120 svn_wc_conflict_reason_t local_change; 1121 svn_wc_operation_t operation; 1122 1123 local_change = svn_client_conflict_get_local_change(conflict); 1124 operation = svn_client_conflict_get_operation(conflict); 1125 1126 switch (local_change) 1127 { 1128 case svn_wc_conflict_reason_edited: 1129 if (operation == svn_wc_operation_update || 1130 operation == svn_wc_operation_switch) 1131 *description = _("A file containing uncommitted changes was " 1132 "found in the working copy."); 1133 else if (operation == svn_wc_operation_merge) 1134 *description = _("A file which differs from the corresponding " 1135 "file on the merge source branch was found " 1136 "in the working copy."); 1137 break; 1138 case svn_wc_conflict_reason_obstructed: 1139 *description = _("A file which already occupies this path was found " 1140 "in the working copy."); 1141 break; 1142 case svn_wc_conflict_reason_unversioned: 1143 *description = _("An unversioned file was found in the working " 1144 "copy."); 1145 break; 1146 case svn_wc_conflict_reason_deleted: 1147 *description = _("A deleted file was found in the working copy."); 1148 break; 1149 case svn_wc_conflict_reason_missing: 1150 if (operation == svn_wc_operation_update || 1151 operation == svn_wc_operation_switch) 1152 *description = _("No such file was found in the working copy."); 1153 else if (operation == svn_wc_operation_merge) 1154 { 1155 /* ### display deleted revision */ 1156 *description = _("No such file was found in the merge target " 1157 "working copy.\nPerhaps the file has been " 1158 "deleted or moved away in the repository's " 1159 "history?"); 1160 } 1161 break; 1162 case svn_wc_conflict_reason_added: 1163 case svn_wc_conflict_reason_replaced: 1164 { 1165 /* ### show more details about copies or replacements? */ 1166 *description = _("A file scheduled to be added to the " 1167 "repository in the next commit was found in " 1168 "the working copy."); 1169 } 1170 break; 1171 case svn_wc_conflict_reason_moved_away: 1172 { 1173 const char *moved_to_abspath; 1174 svn_error_t *err; 1175 1176 err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 1177 ctx->wc_ctx, 1178 conflict->local_abspath, 1179 scratch_pool, 1180 scratch_pool); 1181 if (err) 1182 { 1183 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 1184 { 1185 moved_to_abspath = NULL; 1186 svn_error_clear(err); 1187 } 1188 else 1189 return svn_error_trace(err); 1190 } 1191 if (operation == svn_wc_operation_update || 1192 operation == svn_wc_operation_switch) 1193 { 1194 if (moved_to_abspath == NULL) 1195 { 1196 /* The move no longer exists. */ 1197 *description = _("The file in the working copy had " 1198 "been moved away at the time this " 1199 "conflict was recorded."); 1200 } 1201 else 1202 { 1203 const char *wcroot_abspath; 1204 1205 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, 1206 ctx->wc_ctx, 1207 conflict->local_abspath, 1208 scratch_pool, 1209 scratch_pool)); 1210 *description = apr_psprintf( 1211 result_pool, 1212 _("The file in the working copy was " 1213 "moved away to\n'%s'."), 1214 svn_dirent_local_style( 1215 svn_dirent_skip_ancestor( 1216 wcroot_abspath, 1217 moved_to_abspath), 1218 scratch_pool)); 1219 } 1220 } 1221 else if (operation == svn_wc_operation_merge) 1222 { 1223 if (moved_to_abspath == NULL) 1224 { 1225 /* The move probably happened in branch history. 1226 * This case cannot happen until we detect incoming 1227 * moves, which we currently don't do. */ 1228 /* ### find deleted/moved revision? */ 1229 *description = _("The file in the working copy had " 1230 "been moved away at the time this " 1231 "conflict was recorded."); 1232 } 1233 else 1234 { 1235 /* This is a local move in the working copy. */ 1236 const char *wcroot_abspath; 1237 1238 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, 1239 ctx->wc_ctx, 1240 conflict->local_abspath, 1241 scratch_pool, 1242 scratch_pool)); 1243 *description = apr_psprintf( 1244 result_pool, 1245 _("The file in the working copy was " 1246 "moved away to\n'%s'."), 1247 svn_dirent_local_style( 1248 svn_dirent_skip_ancestor( 1249 wcroot_abspath, 1250 moved_to_abspath), 1251 scratch_pool)); 1252 } 1253 } 1254 break; 1255 } 1256 case svn_wc_conflict_reason_moved_here: 1257 { 1258 const char *moved_from_abspath; 1259 1260 SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, 1261 ctx->wc_ctx, 1262 conflict->local_abspath, 1263 scratch_pool, 1264 scratch_pool)); 1265 if (operation == svn_wc_operation_update || 1266 operation == svn_wc_operation_switch) 1267 { 1268 if (moved_from_abspath == NULL) 1269 { 1270 /* The move no longer exists. */ 1271 *description = _("A file had been moved here in the " 1272 "working copy at the time this " 1273 "conflict was recorded."); 1274 } 1275 else 1276 { 1277 const char *wcroot_abspath; 1278 1279 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, 1280 ctx->wc_ctx, 1281 conflict->local_abspath, 1282 scratch_pool, 1283 scratch_pool)); 1284 *description = apr_psprintf( 1285 result_pool, 1286 _("A file was moved here in the " 1287 "working copy from\n'%s'."), 1288 svn_dirent_local_style( 1289 svn_dirent_skip_ancestor( 1290 wcroot_abspath, 1291 moved_from_abspath), 1292 scratch_pool)); 1293 } 1294 } 1295 else if (operation == svn_wc_operation_merge) 1296 { 1297 if (moved_from_abspath == NULL) 1298 { 1299 /* The move probably happened in branch history. 1300 * This case cannot happen until we detect incoming 1301 * moves, which we currently don't do. */ 1302 /* ### find deleted/moved revision? */ 1303 *description = _("A file had been moved here in the " 1304 "working copy at the time this " 1305 "conflict was recorded."); 1306 } 1307 else 1308 { 1309 const char *wcroot_abspath; 1310 1311 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, 1312 ctx->wc_ctx, 1313 conflict->local_abspath, 1314 scratch_pool, 1315 scratch_pool)); 1316 /* This is a local move in the working copy. */ 1317 *description = apr_psprintf( 1318 result_pool, 1319 _("A file was moved here in the " 1320 "working copy from\n'%s'."), 1321 svn_dirent_local_style( 1322 svn_dirent_skip_ancestor( 1323 wcroot_abspath, 1324 moved_from_abspath), 1325 scratch_pool)); 1326 } 1327 } 1328 break; 1329 } 1330 } 1331 1332 return SVN_NO_ERROR; 1333} 1334 1335/* Return a localised string representation of the local part of a tree 1336 conflict on a directory. */ 1337static svn_error_t * 1338describe_local_dir_node_change(const char **description, 1339 svn_client_conflict_t *conflict, 1340 svn_client_ctx_t *ctx, 1341 apr_pool_t *result_pool, 1342 apr_pool_t *scratch_pool) 1343{ 1344 svn_wc_conflict_reason_t local_change; 1345 svn_wc_operation_t operation; 1346 1347 local_change = svn_client_conflict_get_local_change(conflict); 1348 operation = svn_client_conflict_get_operation(conflict); 1349 1350 switch (local_change) 1351 { 1352 case svn_wc_conflict_reason_edited: 1353 if (operation == svn_wc_operation_update || 1354 operation == svn_wc_operation_switch) 1355 *description = _("A directory containing uncommitted changes " 1356 "was found in the working copy."); 1357 else if (operation == svn_wc_operation_merge) 1358 *description = _("A directory which differs from the " 1359 "corresponding directory on the merge source " 1360 "branch was found in the working copy."); 1361 break; 1362 case svn_wc_conflict_reason_obstructed: 1363 *description = _("A directory which already occupies this path was " 1364 "found in the working copy."); 1365 break; 1366 case svn_wc_conflict_reason_unversioned: 1367 *description = _("An unversioned directory was found in the " 1368 "working copy."); 1369 break; 1370 case svn_wc_conflict_reason_deleted: 1371 *description = _("A deleted directory was found in the " 1372 "working copy."); 1373 break; 1374 case svn_wc_conflict_reason_missing: 1375 if (operation == svn_wc_operation_update || 1376 operation == svn_wc_operation_switch) 1377 *description = _("No such directory was found in the working copy."); 1378 else if (operation == svn_wc_operation_merge) 1379 { 1380 /* ### display deleted revision */ 1381 *description = _("No such directory was found in the merge " 1382 "target working copy.\nPerhaps the " 1383 "directory has been deleted or moved away " 1384 "in the repository's history?"); 1385 } 1386 break; 1387 case svn_wc_conflict_reason_added: 1388 case svn_wc_conflict_reason_replaced: 1389 { 1390 /* ### show more details about copies or replacements? */ 1391 *description = _("A directory scheduled to be added to the " 1392 "repository in the next commit was found in " 1393 "the working copy."); 1394 } 1395 break; 1396 case svn_wc_conflict_reason_moved_away: 1397 { 1398 const char *moved_to_abspath; 1399 svn_error_t *err; 1400 1401 err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 1402 ctx->wc_ctx, 1403 conflict->local_abspath, 1404 scratch_pool, 1405 scratch_pool); 1406 if (err) 1407 { 1408 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 1409 { 1410 moved_to_abspath = NULL; 1411 svn_error_clear(err); 1412 } 1413 else 1414 return svn_error_trace(err); 1415 } 1416 1417 if (operation == svn_wc_operation_update || 1418 operation == svn_wc_operation_switch) 1419 { 1420 if (moved_to_abspath == NULL) 1421 { 1422 /* The move no longer exists. */ 1423 *description = _("The directory in the working copy " 1424 "had been moved away at the time " 1425 "this conflict was recorded."); 1426 } 1427 else 1428 { 1429 const char *wcroot_abspath; 1430 1431 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, 1432 ctx->wc_ctx, 1433 conflict->local_abspath, 1434 scratch_pool, 1435 scratch_pool)); 1436 *description = apr_psprintf( 1437 result_pool, 1438 _("The directory in the working copy " 1439 "was moved away to\n'%s'."), 1440 svn_dirent_local_style( 1441 svn_dirent_skip_ancestor( 1442 wcroot_abspath, 1443 moved_to_abspath), 1444 scratch_pool)); 1445 } 1446 } 1447 else if (operation == svn_wc_operation_merge) 1448 { 1449 if (moved_to_abspath == NULL) 1450 { 1451 /* The move probably happened in branch history. 1452 * This case cannot happen until we detect incoming 1453 * moves, which we currently don't do. */ 1454 /* ### find deleted/moved revision? */ 1455 *description = _("The directory had been moved away " 1456 "at the time this conflict was " 1457 "recorded."); 1458 } 1459 else 1460 { 1461 /* This is a local move in the working copy. */ 1462 const char *wcroot_abspath; 1463 1464 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, 1465 ctx->wc_ctx, 1466 conflict->local_abspath, 1467 scratch_pool, 1468 scratch_pool)); 1469 *description = apr_psprintf( 1470 result_pool, 1471 _("The directory was moved away to\n" 1472 "'%s'."), 1473 svn_dirent_local_style( 1474 svn_dirent_skip_ancestor( 1475 wcroot_abspath, 1476 moved_to_abspath), 1477 scratch_pool)); 1478 } 1479 } 1480 } 1481 break; 1482 case svn_wc_conflict_reason_moved_here: 1483 { 1484 const char *moved_from_abspath; 1485 1486 SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, 1487 ctx->wc_ctx, 1488 conflict->local_abspath, 1489 scratch_pool, 1490 scratch_pool)); 1491 if (operation == svn_wc_operation_update || 1492 operation == svn_wc_operation_switch) 1493 { 1494 if (moved_from_abspath == NULL) 1495 { 1496 /* The move no longer exists. */ 1497 *description = _("A directory had been moved here at " 1498 "the time this conflict was " 1499 "recorded."); 1500 } 1501 else 1502 { 1503 const char *wcroot_abspath; 1504 1505 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, 1506 ctx->wc_ctx, 1507 conflict->local_abspath, 1508 scratch_pool, 1509 scratch_pool)); 1510 *description = apr_psprintf( 1511 result_pool, 1512 _("A directory was moved here from\n" 1513 "'%s'."), 1514 svn_dirent_local_style( 1515 svn_dirent_skip_ancestor( 1516 wcroot_abspath, 1517 moved_from_abspath), 1518 scratch_pool)); 1519 } 1520 } 1521 else if (operation == svn_wc_operation_merge) 1522 { 1523 if (moved_from_abspath == NULL) 1524 { 1525 /* The move probably happened in branch history. 1526 * This case cannot happen until we detect incoming 1527 * moves, which we currently don't do. */ 1528 /* ### find deleted/moved revision? */ 1529 *description = _("A directory had been moved here at " 1530 "the time this conflict was " 1531 "recorded."); 1532 } 1533 else 1534 { 1535 /* This is a local move in the working copy. */ 1536 const char *wcroot_abspath; 1537 1538 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, 1539 ctx->wc_ctx, 1540 conflict->local_abspath, 1541 scratch_pool, 1542 scratch_pool)); 1543 *description = apr_psprintf( 1544 result_pool, 1545 _("A directory was moved here in " 1546 "the working copy from\n'%s'."), 1547 svn_dirent_local_style( 1548 svn_dirent_skip_ancestor( 1549 wcroot_abspath, 1550 moved_from_abspath), 1551 scratch_pool)); 1552 } 1553 } 1554 } 1555 } 1556 1557 return SVN_NO_ERROR; 1558} 1559 1560struct find_moves_baton 1561{ 1562 /* Variables below are arguments provided by the caller of 1563 * svn_ra_get_log2(). */ 1564 const char *repos_root_url; 1565 const char *repos_uuid; 1566 svn_client_ctx_t *ctx; 1567 const char *victim_abspath; /* for notifications */ 1568 apr_pool_t *result_pool; 1569 1570 /* A hash table mapping a revision number to an array of struct 1571 * repos_move_info * elements, describing moves. 1572 * 1573 * Must be allocated in RESULT_POOL by the caller of svn_ra_get_log2(). 1574 * 1575 * If the node was moved, the DELETED_REV is present in this table, 1576 * perhaps along with additional revisions. 1577 * 1578 * Given a sequence of moves which happened in the repository, such as: 1579 * rA: mv x->z 1580 * rA: mv a->b 1581 * rB: mv b->c 1582 * rC: mv c->d 1583 * we map each revision number to all the moves which happened in the 1584 * revision, which looks as follows: 1585 * rA : [(x->z), (a->b)] 1586 * rB : [(b->c)] 1587 * rC : [(c->d)] 1588 * This allows us to later find relevant moves based on a revision number. 1589 * 1590 * Additionally, we embed the number of the revision in which a move was 1591 * found inside the repos_move_info structure: 1592 * rA : [(rA, x->z), (rA, a->b)] 1593 * rB : [(rB, b->c)] 1594 * rC : [(rC, c->d)] 1595 * And also, all moves pertaining to the same node are chained into a 1596 * doubly-linked list via 'next' and 'prev' pointers (see definition of 1597 * struct repos_move_info). This can be visualized as follows: 1598 * rA : [(rA, x->z, prev=>NULL, next=>NULL), 1599 * (rA, a->b, prev=>NULL, next=>(rB, b->c))] 1600 * rB : [(rB, b->c), prev=>(rA, a->b), next=>(rC, c->d)] 1601 * rC : [(rC, c->d), prev=>(rB, c->d), next=>NULL] 1602 * This way, we can look up all moves relevant to a node, forwards and 1603 * backwards in history, once we have located one move in the chain. 1604 * 1605 * In the above example, the data tells us that within the revision 1606 * range rA:C, a was moved to d. However, within the revision range 1607 * rA;B, a was moved to b. 1608 */ 1609 apr_hash_t *moves_table; 1610 1611 /* Variables below hold state for find_moves() and are not 1612 * intended to be used by the caller of svn_ra_get_log2(). 1613 * Like all other variables, they must be initialized, however. */ 1614 1615 /* Temporary map of moved paths to struct repos_move_info. 1616 * Used to link multiple moves of the same node across revisions. */ 1617 apr_hash_t *moved_paths; 1618 1619 /* Extra RA session that can be used to make additional requests. */ 1620 svn_ra_session_t *extra_ra_session; 1621}; 1622 1623/* Implements svn_log_entry_receiver_t. */ 1624static svn_error_t * 1625find_moves(void *baton, svn_log_entry_t *log_entry, apr_pool_t *scratch_pool) 1626{ 1627 struct find_moves_baton *b = baton; 1628 apr_hash_index_t *hi; 1629 apr_pool_t *iterpool; 1630 apr_array_header_t *deleted_paths; 1631 apr_hash_t *copies; 1632 apr_array_header_t *moves; 1633 1634 if (b->ctx->notify_func2) 1635 { 1636 svn_wc_notify_t *notify; 1637 1638 notify = svn_wc_create_notify( 1639 b->victim_abspath, 1640 svn_wc_notify_tree_conflict_details_progress, 1641 scratch_pool), 1642 notify->revision = log_entry->revision; 1643 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool); 1644 } 1645 1646 /* No paths were changed in this revision. Nothing to do. */ 1647 if (! log_entry->changed_paths2) 1648 return SVN_NO_ERROR; 1649 1650 copies = apr_hash_make(scratch_pool); 1651 deleted_paths = apr_array_make(scratch_pool, 0, sizeof(const char *)); 1652 iterpool = svn_pool_create(scratch_pool); 1653 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2); 1654 hi != NULL; 1655 hi = apr_hash_next(hi)) 1656 { 1657 const char *changed_path = apr_hash_this_key(hi); 1658 svn_log_changed_path2_t *log_item = apr_hash_this_val(hi); 1659 1660 svn_pool_clear(iterpool); 1661 1662 /* ### Remove leading slash from paths in log entries. */ 1663 if (changed_path[0] == '/') 1664 changed_path++; 1665 1666 /* For move detection, scan for copied nodes in this revision. */ 1667 if (log_item->action == 'A' && log_item->copyfrom_path) 1668 cache_copied_item(copies, changed_path, log_item); 1669 1670 /* For move detection, store all deleted_paths. */ 1671 if (log_item->action == 'D' || log_item->action == 'R') 1672 APR_ARRAY_PUSH(deleted_paths, const char *) = 1673 apr_pstrdup(scratch_pool, changed_path); 1674 } 1675 svn_pool_destroy(iterpool); 1676 1677 /* Check for moves in this revision */ 1678 SVN_ERR(find_moves_in_revision(b->extra_ra_session, 1679 b->moves_table, b->moved_paths, 1680 log_entry, copies, deleted_paths, 1681 b->repos_root_url, b->repos_uuid, 1682 b->ctx, b->result_pool, scratch_pool)); 1683 1684 moves = apr_hash_get(b->moves_table, &log_entry->revision, 1685 sizeof(svn_revnum_t)); 1686 if (moves) 1687 { 1688 const svn_string_t *author; 1689 1690 author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR); 1691 SVN_ERR(find_nested_moves(moves, copies, deleted_paths, 1692 b->moved_paths, log_entry->revision, 1693 author ? author->data : _("unknown author"), 1694 b->repos_root_url, 1695 b->repos_uuid, 1696 b->extra_ra_session, b->ctx, 1697 b->result_pool, scratch_pool)); 1698 } 1699 1700 return SVN_NO_ERROR; 1701} 1702 1703/* Find all moves which occured in repository history starting at 1704 * REPOS_RELPATH@START_REV until END_REV (where START_REV > END_REV). 1705 * Return results in *MOVES_TABLE (see struct find_moves_baton for details). */ 1706static svn_error_t * 1707find_moves_in_revision_range(struct apr_hash_t **moves_table, 1708 const char *repos_relpath, 1709 const char *repos_root_url, 1710 const char *repos_uuid, 1711 const char *victim_abspath, 1712 svn_revnum_t start_rev, 1713 svn_revnum_t end_rev, 1714 svn_client_ctx_t *ctx, 1715 apr_pool_t *result_pool, 1716 apr_pool_t *scratch_pool) 1717{ 1718 svn_ra_session_t *ra_session; 1719 const char *url; 1720 const char *corrected_url; 1721 apr_array_header_t *paths; 1722 apr_array_header_t *revprops; 1723 struct find_moves_baton b = { 0 }; 1724 1725 SVN_ERR_ASSERT(start_rev > end_rev); 1726 1727 url = svn_path_url_add_component2(repos_root_url, repos_relpath, 1728 scratch_pool); 1729 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 1730 url, NULL, NULL, FALSE, FALSE, 1731 ctx, scratch_pool, 1732 scratch_pool)); 1733 1734 paths = apr_array_make(scratch_pool, 1, sizeof(const char *)); 1735 APR_ARRAY_PUSH(paths, const char *) = ""; 1736 1737 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *)); 1738 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; 1739 1740 b.repos_root_url = repos_root_url; 1741 b.repos_uuid = repos_uuid; 1742 b.ctx = ctx; 1743 b.victim_abspath = victim_abspath; 1744 b.moves_table = apr_hash_make(result_pool); 1745 b.moved_paths = apr_hash_make(scratch_pool); 1746 b.result_pool = result_pool; 1747 SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL, 1748 scratch_pool, scratch_pool)); 1749 1750 SVN_ERR(svn_ra_get_log2(ra_session, paths, start_rev, end_rev, 1751 0, /* no limit */ 1752 TRUE, /* need the changed paths list */ 1753 FALSE, /* need to traverse copies */ 1754 FALSE, /* no need for merged revisions */ 1755 revprops, 1756 find_moves, &b, 1757 scratch_pool)); 1758 1759 *moves_table = b.moves_table; 1760 1761 return SVN_NO_ERROR; 1762} 1763 1764/* Return new move information for a moved-along child MOVED_ALONG_RELPATH. 1765 * Set MOVE->NODE_KIND to MOVED_ALONG_NODE_KIND. 1766 * Do not copy MOVE->NEXT and MOVE-PREV. 1767 * If MOVED_ALONG_RELPATH is empty, this effectively copies MOVE to 1768 * RESULT_POOL with NEXT and PREV pointers cleared. */ 1769static struct repos_move_info * 1770new_path_adjusted_move(struct repos_move_info *move, 1771 const char *moved_along_relpath, 1772 svn_node_kind_t moved_along_node_kind, 1773 apr_pool_t *result_pool) 1774{ 1775 struct repos_move_info *new_move; 1776 1777 new_move = apr_pcalloc(result_pool, sizeof(*new_move)); 1778 new_move->moved_from_repos_relpath = 1779 svn_relpath_join(move->moved_from_repos_relpath, moved_along_relpath, 1780 result_pool); 1781 new_move->moved_to_repos_relpath = 1782 svn_relpath_join(move->moved_to_repos_relpath, moved_along_relpath, 1783 result_pool); 1784 new_move->rev = move->rev; 1785 new_move->rev_author = apr_pstrdup(result_pool, move->rev_author); 1786 new_move->copyfrom_rev = move->copyfrom_rev; 1787 new_move->node_kind = moved_along_node_kind; 1788 /* Ignore prev and next pointers. Caller will set them if needed. */ 1789 1790 return new_move; 1791} 1792 1793/* Given a list of MOVES_IN_REVISION, figure out which of these moves again 1794 * move the node which was already moved by PREV_MOVE in the past . */ 1795static svn_error_t * 1796find_next_moves_in_revision(apr_array_header_t **next_moves, 1797 apr_array_header_t *moves_in_revision, 1798 struct repos_move_info *prev_move, 1799 svn_ra_session_t *ra_session, 1800 const char *repos_root_url, 1801 apr_pool_t *result_pool, 1802 apr_pool_t *scratch_pool) 1803{ 1804 int i; 1805 apr_pool_t *iterpool; 1806 1807 iterpool = svn_pool_create(scratch_pool); 1808 for (i = 0; i < moves_in_revision->nelts; i++) 1809 { 1810 struct repos_move_info *move; 1811 const char *relpath; 1812 const char *deleted_repos_relpath; 1813 svn_boolean_t related; 1814 svn_error_t *err; 1815 1816 svn_pool_clear(iterpool); 1817 1818 /* Check if this move affects the current known path of our node. */ 1819 move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *); 1820 relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath, 1821 prev_move->moved_to_repos_relpath); 1822 if (relpath == NULL) 1823 continue; 1824 1825 /* It does. So our node must have been deleted again. */ 1826 deleted_repos_relpath = svn_relpath_join(move->moved_from_repos_relpath, 1827 relpath, iterpool); 1828 1829 /* Tracing back history of the delete-half of this move to the 1830 * copyfrom-revision of the prior move we must end up at the 1831 * delete-half of the prior move. */ 1832 err = check_move_ancestry(&related, ra_session, repos_root_url, 1833 deleted_repos_relpath, move->rev, 1834 prev_move->moved_from_repos_relpath, 1835 prev_move->copyfrom_rev, 1836 FALSE, scratch_pool); 1837 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 1838 { 1839 svn_error_clear(err); 1840 continue; 1841 } 1842 else 1843 SVN_ERR(err); 1844 1845 if (related) 1846 { 1847 struct repos_move_info *new_move; 1848 1849 /* We have a winner. */ 1850 new_move = new_path_adjusted_move(move, relpath, prev_move->node_kind, 1851 result_pool); 1852 if (*next_moves == NULL) 1853 *next_moves = apr_array_make(result_pool, 1, 1854 sizeof(struct repos_move_info *)); 1855 APR_ARRAY_PUSH(*next_moves, struct repos_move_info *) = new_move; 1856 } 1857 } 1858 svn_pool_destroy(iterpool); 1859 1860 return SVN_NO_ERROR; 1861} 1862 1863static int 1864compare_items_as_revs(const svn_sort__item_t *a, const svn_sort__item_t *b) 1865{ 1866 return svn_sort_compare_revisions(a->key, b->key); 1867} 1868 1869/* Starting at MOVE->REV, loop over future revisions which contain moves, 1870 * and look for matching next moves in each. Once found, return a list of 1871 * (ambiguous, if more than one) moves in *NEXT_MOVES. */ 1872static svn_error_t * 1873find_next_moves(apr_array_header_t **next_moves, 1874 apr_hash_t *moves_table, 1875 struct repos_move_info *move, 1876 svn_ra_session_t *ra_session, 1877 const char *repos_root_url, 1878 apr_pool_t *result_pool, 1879 apr_pool_t *scratch_pool) 1880{ 1881 apr_array_header_t *moves; 1882 apr_array_header_t *revisions; 1883 apr_pool_t *iterpool; 1884 int i; 1885 1886 *next_moves = NULL; 1887 revisions = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool); 1888 iterpool = svn_pool_create(scratch_pool); 1889 for (i = 0; i < revisions->nelts; i++) 1890 { 1891 svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t); 1892 svn_revnum_t rev = *(svn_revnum_t *)item.key; 1893 1894 svn_pool_clear(iterpool); 1895 1896 if (rev <= move->rev) 1897 continue; 1898 1899 moves = apr_hash_get(moves_table, &rev, sizeof(rev)); 1900 SVN_ERR(find_next_moves_in_revision(next_moves, moves, move, 1901 ra_session, repos_root_url, 1902 result_pool, iterpool)); 1903 if (*next_moves) 1904 break; 1905 } 1906 svn_pool_destroy(iterpool); 1907 1908 return SVN_NO_ERROR; 1909} 1910 1911/* Trace all future moves of the node moved by MOVE. 1912 * Update MOVE->PREV and MOVE->NEXT accordingly. */ 1913static svn_error_t * 1914trace_moved_node(apr_hash_t *moves_table, 1915 struct repos_move_info *move, 1916 svn_ra_session_t *ra_session, 1917 const char *repos_root_url, 1918 apr_pool_t *result_pool, 1919 apr_pool_t *scratch_pool) 1920{ 1921 apr_array_header_t *next_moves; 1922 1923 SVN_ERR(find_next_moves(&next_moves, moves_table, move, 1924 ra_session, repos_root_url, 1925 result_pool, scratch_pool)); 1926 if (next_moves) 1927 { 1928 int i; 1929 apr_pool_t *iterpool; 1930 1931 move->next = next_moves; 1932 iterpool = svn_pool_create(scratch_pool); 1933 for (i = 0; i < next_moves->nelts; i++) 1934 { 1935 struct repos_move_info *next_move; 1936 1937 svn_pool_clear(iterpool); 1938 next_move = APR_ARRAY_IDX(next_moves, i, struct repos_move_info *); 1939 next_move->prev = move; 1940 SVN_ERR(trace_moved_node(moves_table, next_move, 1941 ra_session, repos_root_url, 1942 result_pool, iterpool)); 1943 } 1944 svn_pool_destroy(iterpool); 1945 } 1946 1947 return SVN_NO_ERROR; 1948} 1949 1950/* Given a list of MOVES_IN_REVISION, figure out which of these moves 1951 * move the node which was later on moved by NEXT_MOVE. */ 1952static svn_error_t * 1953find_prev_move_in_revision(struct repos_move_info **prev_move, 1954 apr_array_header_t *moves_in_revision, 1955 struct repos_move_info *next_move, 1956 svn_ra_session_t *ra_session, 1957 const char *repos_root_url, 1958 apr_pool_t *result_pool, 1959 apr_pool_t *scratch_pool) 1960{ 1961 int i; 1962 apr_pool_t *iterpool; 1963 1964 *prev_move = NULL; 1965 1966 iterpool = svn_pool_create(scratch_pool); 1967 for (i = 0; i < moves_in_revision->nelts; i++) 1968 { 1969 struct repos_move_info *move; 1970 const char *relpath; 1971 const char *deleted_repos_relpath; 1972 svn_boolean_t related; 1973 svn_error_t *err; 1974 1975 svn_pool_clear(iterpool); 1976 1977 /* Check if this move affects the current known path of our node. */ 1978 move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *); 1979 relpath = svn_relpath_skip_ancestor(next_move->moved_from_repos_relpath, 1980 move->moved_to_repos_relpath); 1981 if (relpath == NULL) 1982 continue; 1983 1984 /* It does. So our node must have been deleted. */ 1985 deleted_repos_relpath = svn_relpath_join( 1986 next_move->moved_from_repos_relpath, 1987 relpath, iterpool); 1988 1989 /* Tracing back history of the delete-half of the next move to the 1990 * copyfrom-revision of the prior move we must end up at the 1991 * delete-half of the prior move. */ 1992 err = check_move_ancestry(&related, ra_session, repos_root_url, 1993 deleted_repos_relpath, next_move->rev, 1994 move->moved_from_repos_relpath, 1995 move->copyfrom_rev, 1996 FALSE, scratch_pool); 1997 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 1998 { 1999 svn_error_clear(err); 2000 continue; 2001 } 2002 else 2003 SVN_ERR(err); 2004 2005 if (related) 2006 { 2007 /* We have a winner. */ 2008 *prev_move = new_path_adjusted_move(move, relpath, 2009 next_move->node_kind, 2010 result_pool); 2011 break; 2012 } 2013 } 2014 svn_pool_destroy(iterpool); 2015 2016 return SVN_NO_ERROR; 2017} 2018 2019static int 2020compare_items_as_revs_reverse(const svn_sort__item_t *a, 2021 const svn_sort__item_t *b) 2022{ 2023 int c = svn_sort_compare_revisions(a->key, b->key); 2024 if (c < 0) 2025 return 1; 2026 if (c > 0) 2027 return -1; 2028 return c; 2029} 2030 2031/* Starting at MOVE->REV, loop over past revisions which contain moves, 2032 * and look for a matching previous move in each. Once found, return 2033 * it in *PREV_MOVE */ 2034static svn_error_t * 2035find_prev_move(struct repos_move_info **prev_move, 2036 apr_hash_t *moves_table, 2037 struct repos_move_info *move, 2038 svn_ra_session_t *ra_session, 2039 const char *repos_root_url, 2040 apr_pool_t *result_pool, 2041 apr_pool_t *scratch_pool) 2042{ 2043 apr_array_header_t *moves; 2044 apr_array_header_t *revisions; 2045 apr_pool_t *iterpool; 2046 int i; 2047 2048 *prev_move = NULL; 2049 revisions = svn_sort__hash(moves_table, compare_items_as_revs_reverse, 2050 scratch_pool); 2051 iterpool = svn_pool_create(scratch_pool); 2052 for (i = 0; i < revisions->nelts; i++) 2053 { 2054 svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t); 2055 svn_revnum_t rev = *(svn_revnum_t *)item.key; 2056 2057 svn_pool_clear(iterpool); 2058 2059 if (rev >= move->rev) 2060 continue; 2061 2062 moves = apr_hash_get(moves_table, &rev, sizeof(rev)); 2063 SVN_ERR(find_prev_move_in_revision(prev_move, moves, move, 2064 ra_session, repos_root_url, 2065 result_pool, iterpool)); 2066 if (*prev_move) 2067 break; 2068 } 2069 svn_pool_destroy(iterpool); 2070 2071 return SVN_NO_ERROR; 2072} 2073 2074 2075/* Trace all past moves of the node moved by MOVE. 2076 * Update MOVE->PREV and MOVE->NEXT accordingly. */ 2077static svn_error_t * 2078trace_moved_node_backwards(apr_hash_t *moves_table, 2079 struct repos_move_info *move, 2080 svn_ra_session_t *ra_session, 2081 const char *repos_root_url, 2082 apr_pool_t *result_pool, 2083 apr_pool_t *scratch_pool) 2084{ 2085 struct repos_move_info *prev_move; 2086 2087 SVN_ERR(find_prev_move(&prev_move, moves_table, move, 2088 ra_session, repos_root_url, 2089 result_pool, scratch_pool)); 2090 if (prev_move) 2091 { 2092 move->prev = prev_move; 2093 prev_move->next = apr_array_make(result_pool, 1, 2094 sizeof(struct repos_move_info *)); 2095 APR_ARRAY_PUSH(prev_move->next, struct repos_move_info *) = move; 2096 2097 SVN_ERR(trace_moved_node_backwards(moves_table, prev_move, 2098 ra_session, repos_root_url, 2099 result_pool, scratch_pool)); 2100 } 2101 2102 return SVN_NO_ERROR; 2103} 2104 2105/* Scan MOVES_TABLE for moves which affect a particular deleted node, and 2106 * build a set of new move information for this node. 2107 * Return heads of all possible move chains in *MOVES. 2108 * 2109 * MOVES_TABLE describes moves which happened at arbitrary paths in the 2110 * repository. DELETED_REPOS_RELPATH may have been moved directly or it 2111 * may have been moved along with a parent path. Move information returned 2112 * from this function represents how DELETED_REPOS_RELPATH itself was moved 2113 * from one path to another, effectively "zooming in" on the effective move 2114 * operations which occurred for this particular node. */ 2115static svn_error_t * 2116find_operative_moves(apr_array_header_t **moves, 2117 apr_hash_t *moves_table, 2118 const char *deleted_repos_relpath, 2119 svn_revnum_t deleted_rev, 2120 svn_ra_session_t *ra_session, 2121 const char *repos_root_url, 2122 apr_pool_t *result_pool, 2123 apr_pool_t *scratch_pool) 2124{ 2125 apr_array_header_t *moves_in_deleted_rev; 2126 int i; 2127 apr_pool_t *iterpool; 2128 const char *session_url, *url = NULL; 2129 2130 moves_in_deleted_rev = apr_hash_get(moves_table, &deleted_rev, 2131 sizeof(deleted_rev)); 2132 if (moves_in_deleted_rev == NULL) 2133 { 2134 *moves = NULL; 2135 return SVN_NO_ERROR; 2136 } 2137 2138 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool)); 2139 2140 /* Look for operative moves in the revision where the node was deleted. */ 2141 *moves = apr_array_make(scratch_pool, 0, sizeof(struct repos_move_info *)); 2142 iterpool = svn_pool_create(scratch_pool); 2143 for (i = 0; i < moves_in_deleted_rev->nelts; i++) 2144 { 2145 struct repos_move_info *move; 2146 const char *relpath; 2147 2148 svn_pool_clear(iterpool); 2149 2150 move = APR_ARRAY_IDX(moves_in_deleted_rev, i, struct repos_move_info *); 2151 if (strcmp(move->moved_from_repos_relpath, deleted_repos_relpath) == 0) 2152 { 2153 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move; 2154 continue; 2155 } 2156 2157 /* Test for an operative nested move. */ 2158 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath, 2159 deleted_repos_relpath); 2160 if (relpath && relpath[0] != '\0') 2161 { 2162 struct repos_move_info *nested_move; 2163 const char *actual_deleted_repos_relpath; 2164 2165 actual_deleted_repos_relpath = 2166 svn_relpath_join(move->moved_from_repos_relpath, relpath, 2167 iterpool); 2168 nested_move = map_deleted_path_to_move(actual_deleted_repos_relpath, 2169 moves_in_deleted_rev, 2170 iterpool); 2171 if (nested_move) 2172 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = nested_move; 2173 } 2174 } 2175 2176 if (url != NULL) 2177 SVN_ERR(svn_ra_reparent(ra_session, session_url, scratch_pool)); 2178 2179 /* If we didn't find any applicable moves, return NULL. */ 2180 if ((*moves)->nelts == 0) 2181 { 2182 *moves = NULL; 2183 svn_pool_destroy(iterpool); 2184 return SVN_NO_ERROR; 2185 } 2186 2187 /* Figure out what happened to these moves in future revisions. */ 2188 for (i = 0; i < (*moves)->nelts; i++) 2189 { 2190 struct repos_move_info *move; 2191 2192 svn_pool_clear(iterpool); 2193 2194 move = APR_ARRAY_IDX(*moves, i, struct repos_move_info *); 2195 SVN_ERR(trace_moved_node(moves_table, move, ra_session, repos_root_url, 2196 result_pool, iterpool)); 2197 } 2198 2199 svn_pool_destroy(iterpool); 2200 return SVN_NO_ERROR; 2201} 2202 2203/* Try to find a revision older than START_REV, and its author, which deleted 2204 * DELETED_BASENAME in the directory PARENT_REPOS_RELPATH. Assume the deleted 2205 * node is ancestrally related to RELATED_REPOS_RELPATH@RELATED_PEG_REV. 2206 * If no such revision can be found, set *DELETED_REV to SVN_INVALID_REVNUM 2207 * and *DELETED_REV_AUTHOR to NULL. 2208 * If the node was replaced rather than deleted, set *REPLACING_NODE_KIND to 2209 * the node kind of the replacing node. Else, set it to svn_node_unknown. 2210 * Only request the log for revisions up to END_REV from the server. 2211 * If MOVES it not NULL, and the deleted node was moved, provide heads of 2212 * move chains in *MOVES, or, if the node was not moved, set *MOVES to NULL. 2213 */ 2214static svn_error_t * 2215find_revision_for_suspected_deletion(svn_revnum_t *deleted_rev, 2216 const char **deleted_rev_author, 2217 svn_node_kind_t *replacing_node_kind, 2218 struct apr_array_header_t **moves, 2219 svn_client_conflict_t *conflict, 2220 const char *deleted_basename, 2221 const char *parent_repos_relpath, 2222 svn_revnum_t start_rev, 2223 svn_revnum_t end_rev, 2224 const char *related_repos_relpath, 2225 svn_revnum_t related_peg_rev, 2226 svn_client_ctx_t *ctx, 2227 apr_pool_t *result_pool, 2228 apr_pool_t *scratch_pool) 2229{ 2230 svn_ra_session_t *ra_session; 2231 const char *url; 2232 const char *corrected_url; 2233 apr_array_header_t *paths; 2234 apr_array_header_t *revprops; 2235 const char *repos_root_url; 2236 const char *repos_uuid; 2237 struct find_deleted_rev_baton b = { 0 }; 2238 const char *victim_abspath; 2239 svn_error_t *err; 2240 apr_hash_t *moves_table; 2241 2242 SVN_ERR_ASSERT(start_rev > end_rev); 2243 2244 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid, 2245 conflict, scratch_pool, 2246 scratch_pool)); 2247 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 2248 2249 if (moves) 2250 SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath, 2251 repos_root_url, repos_uuid, 2252 victim_abspath, start_rev, end_rev, 2253 ctx, result_pool, scratch_pool)); 2254 2255 url = svn_path_url_add_component2(repos_root_url, parent_repos_relpath, 2256 scratch_pool); 2257 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 2258 url, NULL, NULL, FALSE, FALSE, 2259 ctx, scratch_pool, 2260 scratch_pool)); 2261 2262 paths = apr_array_make(scratch_pool, 1, sizeof(const char *)); 2263 APR_ARRAY_PUSH(paths, const char *) = ""; 2264 2265 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *)); 2266 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; 2267 2268 b.victim_abspath = victim_abspath; 2269 b.deleted_repos_relpath = svn_relpath_join(parent_repos_relpath, 2270 deleted_basename, scratch_pool); 2271 b.related_repos_relpath = related_repos_relpath; 2272 b.related_peg_rev = related_peg_rev; 2273 b.deleted_rev = SVN_INVALID_REVNUM; 2274 b.replacing_node_kind = svn_node_unknown; 2275 b.repos_root_url = repos_root_url; 2276 b.repos_uuid = repos_uuid; 2277 b.ctx = ctx; 2278 if (moves) 2279 b.moves_table = moves_table; 2280 b.result_pool = result_pool; 2281 SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL, 2282 scratch_pool, scratch_pool)); 2283 2284 err = svn_ra_get_log2(ra_session, paths, start_rev, end_rev, 2285 0, /* no limit */ 2286 TRUE, /* need the changed paths list */ 2287 FALSE, /* need to traverse copies */ 2288 FALSE, /* no need for merged revisions */ 2289 revprops, 2290 find_deleted_rev, &b, 2291 scratch_pool); 2292 if (err) 2293 { 2294 if (err->apr_err == SVN_ERR_CEASE_INVOCATION && 2295 b.deleted_rev != SVN_INVALID_REVNUM) 2296 2297 { 2298 /* Log operation was aborted because we found deleted rev. */ 2299 svn_error_clear(err); 2300 } 2301 else 2302 return svn_error_trace(err); 2303 } 2304 2305 if (b.deleted_rev == SVN_INVALID_REVNUM) 2306 { 2307 struct repos_move_info *move = b.move; 2308 2309 if (moves && move) 2310 { 2311 *deleted_rev = move->rev; 2312 *deleted_rev_author = move->rev_author; 2313 *replacing_node_kind = b.replacing_node_kind; 2314 SVN_ERR(find_operative_moves(moves, moves_table, 2315 b.deleted_repos_relpath, 2316 move->rev, 2317 ra_session, repos_root_url, 2318 result_pool, scratch_pool)); 2319 } 2320 else 2321 { 2322 /* We could not determine the revision in which the node was 2323 * deleted. */ 2324 *deleted_rev = SVN_INVALID_REVNUM; 2325 *deleted_rev_author = NULL; 2326 *replacing_node_kind = svn_node_unknown; 2327 if (moves) 2328 *moves = NULL; 2329 } 2330 return SVN_NO_ERROR; 2331 } 2332 else 2333 { 2334 *deleted_rev = b.deleted_rev; 2335 *deleted_rev_author = b.deleted_rev_author; 2336 *replacing_node_kind = b.replacing_node_kind; 2337 if (moves) 2338 SVN_ERR(find_operative_moves(moves, moves_table, 2339 b.deleted_repos_relpath, b.deleted_rev, 2340 ra_session, repos_root_url, 2341 result_pool, scratch_pool)); 2342 } 2343 2344 return SVN_NO_ERROR; 2345} 2346 2347/* Details for tree conflicts involving a locally missing node. */ 2348struct conflict_tree_local_missing_details 2349{ 2350 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */ 2351 svn_revnum_t deleted_rev; 2352 2353 /* ### Add 'added_rev', like in conflict_tree_incoming_delete_details? */ 2354 2355 /* Author who committed DELETED_REV. */ 2356 const char *deleted_rev_author; 2357 2358 /* The path which was deleted relative to the repository root. */ 2359 const char *deleted_repos_relpath; 2360 2361 /* Move information about the conflict victim. If not NULL, this is an 2362 * array of 'struct repos_move_info *' elements. Each element is the 2363 * head of a move chain which starts in DELETED_REV. */ 2364 apr_array_header_t *moves; 2365 2366 /* If moves is not NULL, a map of repos_relpaths and working copy nodes. 2367 * 2368 * Each key is a "const char *" repository relpath corresponding to a 2369 * possible repository-side move destination node in the revision which 2370 * is the merge-right revision in case of a merge. 2371 * 2372 * Each value is an apr_array_header_t *. 2373 * Each array consists of "const char *" absolute paths to working copy 2374 * nodes which correspond to the repository node selected by the map key. 2375 * Each such working copy node is a potential local move target which can 2376 * be chosen to find a suitable merge target when resolving a tree conflict. 2377 * 2378 * This may be an empty hash map in case if there is no move target path 2379 * in the working copy. */ 2380 apr_hash_t *wc_move_targets; 2381 2382 /* If not NULL, the preferred move target repository relpath. This is our key 2383 * into the WC_MOVE_TARGETS map above (can be overridden by the user). */ 2384 const char *move_target_repos_relpath; 2385 2386 /* The current index into the list of working copy nodes corresponding to 2387 * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */ 2388 int wc_move_target_idx; 2389 2390 /* Move information about siblings. Siblings are nodes which share 2391 * a youngest common ancestor with the conflict victim. E.g. in case 2392 * of a merge operation they are part of the merge source branch. 2393 * If not NULL, this is an array of 'struct repos_move_info *' elements. 2394 * Each element is the head of a move chain, which starts at some 2395 * point in history after siblings and conflict victim forked off 2396 * their common ancestor. */ 2397 apr_array_header_t *sibling_moves; 2398 2399 /* List of nodes in the WC which are suitable merge targets for changes 2400 * merged from any moved sibling. Array elements are 'const char *' 2401 * absolute paths of working copy nodes. This array contains multiple 2402 * elements only if ambiguous matches were found in the WC. */ 2403 apr_array_header_t *wc_siblings; 2404 int preferred_sibling_idx; 2405}; 2406 2407static svn_error_t * 2408find_related_node(const char **related_repos_relpath, 2409 svn_revnum_t *related_peg_rev, 2410 const char *younger_related_repos_relpath, 2411 svn_revnum_t younger_related_peg_rev, 2412 const char *older_repos_relpath, 2413 svn_revnum_t older_peg_rev, 2414 svn_client_conflict_t *conflict, 2415 svn_client_ctx_t *ctx, 2416 apr_pool_t *result_pool, 2417 apr_pool_t *scratch_pool) 2418{ 2419 const char *repos_root_url; 2420 const char *related_url; 2421 const char *corrected_url; 2422 svn_node_kind_t related_node_kind; 2423 svn_ra_session_t *ra_session; 2424 2425 *related_repos_relpath = NULL; 2426 *related_peg_rev = SVN_INVALID_REVNUM; 2427 2428 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 2429 conflict, 2430 scratch_pool, scratch_pool)); 2431 related_url = svn_path_url_add_component2(repos_root_url, 2432 younger_related_repos_relpath, 2433 scratch_pool); 2434 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, 2435 &corrected_url, 2436 related_url, NULL, 2437 NULL, 2438 FALSE, 2439 FALSE, 2440 ctx, 2441 scratch_pool, 2442 scratch_pool)); 2443 SVN_ERR(svn_ra_check_path(ra_session, "", younger_related_peg_rev, 2444 &related_node_kind, scratch_pool)); 2445 if (related_node_kind == svn_node_none) 2446 { 2447 svn_revnum_t related_deleted_rev; 2448 const char *related_deleted_rev_author; 2449 svn_node_kind_t related_replacing_node_kind; 2450 const char *related_basename; 2451 const char *related_parent_repos_relpath; 2452 apr_array_header_t *related_moves; 2453 2454 /* Looks like the younger node, which we'd like to use as our 2455 * 'related node', was deleted. Try to find its deleted revision 2456 * so we can calculate a peg revision at which it exists. 2457 * The younger node is related to the older node, so we can use 2458 * the older node to guide us in our search. */ 2459 related_basename = svn_relpath_basename(younger_related_repos_relpath, 2460 scratch_pool); 2461 related_parent_repos_relpath = 2462 svn_relpath_dirname(younger_related_repos_relpath, scratch_pool); 2463 SVN_ERR(find_revision_for_suspected_deletion( 2464 &related_deleted_rev, &related_deleted_rev_author, 2465 &related_replacing_node_kind, &related_moves, 2466 conflict, related_basename, 2467 related_parent_repos_relpath, 2468 younger_related_peg_rev, 0, 2469 older_repos_relpath, older_peg_rev, 2470 ctx, conflict->pool, scratch_pool)); 2471 2472 /* If we can't find a related node, bail. */ 2473 if (related_deleted_rev == SVN_INVALID_REVNUM) 2474 return SVN_NO_ERROR; 2475 2476 /* The node should exist in the revision before it was deleted. */ 2477 *related_repos_relpath = younger_related_repos_relpath; 2478 *related_peg_rev = rev_below(related_deleted_rev); 2479 } 2480 else 2481 { 2482 *related_repos_relpath = younger_related_repos_relpath; 2483 *related_peg_rev = younger_related_peg_rev; 2484 } 2485 2486 return SVN_NO_ERROR; 2487} 2488 2489/* Determine if REPOS_RELPATH@PEG_REV was moved at some point in its history. 2490 * History's range of interest ends at END_REV which must be older than PEG_REV. 2491 * 2492 * VICTIM_ABSPATH is the abspath of a conflict victim in the working copy and 2493 * will be used in notifications. 2494 * 2495 * Return any applicable move chain heads in *MOVES. 2496 * If no moves can be found, set *MOVES to NULL. */ 2497static svn_error_t * 2498find_moves_in_natural_history(apr_array_header_t **moves, 2499 const char *repos_relpath, 2500 svn_revnum_t peg_rev, 2501 svn_node_kind_t node_kind, 2502 svn_revnum_t end_rev, 2503 const char *victim_abspath, 2504 const char *repos_root_url, 2505 const char *repos_uuid, 2506 svn_ra_session_t *ra_session, 2507 svn_client_ctx_t *ctx, 2508 apr_pool_t *result_pool, 2509 apr_pool_t *scratch_pool) 2510{ 2511 apr_hash_t *moves_table; 2512 apr_array_header_t *revs; 2513 apr_array_header_t *most_recent_moves = NULL; 2514 int i; 2515 apr_pool_t *iterpool; 2516 2517 *moves = NULL; 2518 2519 SVN_ERR(find_moves_in_revision_range(&moves_table, repos_relpath, 2520 repos_root_url, repos_uuid, 2521 victim_abspath, peg_rev, end_rev, 2522 ctx, scratch_pool, scratch_pool)); 2523 2524 iterpool = svn_pool_create(scratch_pool); 2525 2526 /* Scan the moves table for applicable moves. */ 2527 revs = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool); 2528 for (i = revs->nelts - 1; i >= 0; i--) 2529 { 2530 svn_sort__item_t item = APR_ARRAY_IDX(revs, i, svn_sort__item_t); 2531 apr_array_header_t *moves_in_rev = apr_hash_get(moves_table, item.key, 2532 sizeof(svn_revnum_t)); 2533 int j; 2534 2535 svn_pool_clear(iterpool); 2536 2537 /* Was repos relpath moved to its location in this revision? */ 2538 for (j = 0; j < moves_in_rev->nelts; j++) 2539 { 2540 struct repos_move_info *move; 2541 const char *relpath; 2542 2543 move = APR_ARRAY_IDX(moves_in_rev, j, struct repos_move_info *); 2544 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath, 2545 repos_relpath); 2546 if (relpath) 2547 { 2548 /* If the move did not happen in our peg revision, make 2549 * sure this move happened on the same line of history. */ 2550 if (move->rev != peg_rev) 2551 { 2552 svn_client__pathrev_t *yca_loc; 2553 svn_error_t *err; 2554 2555 err = find_yca(&yca_loc, repos_relpath, peg_rev, 2556 repos_relpath, move->rev, 2557 repos_root_url, repos_uuid, 2558 NULL, ctx, iterpool, iterpool); 2559 if (err) 2560 { 2561 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 2562 { 2563 svn_error_clear(err); 2564 yca_loc = NULL; 2565 } 2566 else 2567 return svn_error_trace(err); 2568 } 2569 2570 if (yca_loc == NULL || yca_loc->rev != move->rev) 2571 continue; 2572 } 2573 2574 if (most_recent_moves == NULL) 2575 most_recent_moves = 2576 apr_array_make(result_pool, 1, 2577 sizeof(struct repos_move_info *)); 2578 2579 /* Copy the move to result pool (even if relpath is ""). */ 2580 move = new_path_adjusted_move(move, relpath, node_kind, 2581 result_pool); 2582 APR_ARRAY_PUSH(most_recent_moves, 2583 struct repos_move_info *) = move; 2584 } 2585 } 2586 2587 /* If we found one move, or several ambiguous moves, we're done. */ 2588 if (most_recent_moves) 2589 break; 2590 } 2591 2592 if (most_recent_moves && most_recent_moves->nelts > 0) 2593 { 2594 *moves = apr_array_make(result_pool, 1, 2595 sizeof(struct repos_move_info *)); 2596 2597 /* Figure out what happened to the most recent moves in prior 2598 * revisions and build move chains. */ 2599 for (i = 0; i < most_recent_moves->nelts; i++) 2600 { 2601 struct repos_move_info *move; 2602 2603 svn_pool_clear(iterpool); 2604 2605 move = APR_ARRAY_IDX(most_recent_moves, i, struct repos_move_info *); 2606 SVN_ERR(trace_moved_node_backwards(moves_table, move, 2607 ra_session, repos_root_url, 2608 result_pool, iterpool)); 2609 /* Follow the move chain backwards. */ 2610 while (move->prev) 2611 move = move->prev; 2612 2613 /* Return move heads. */ 2614 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move; 2615 } 2616 } 2617 2618 svn_pool_destroy(iterpool); 2619 2620 return SVN_NO_ERROR; 2621} 2622 2623static svn_error_t * 2624collect_sibling_move_candidates(apr_array_header_t *candidates, 2625 const char *victim_abspath, 2626 svn_node_kind_t victim_kind, 2627 struct repos_move_info *move, 2628 svn_client_ctx_t *ctx, 2629 apr_pool_t *result_pool, 2630 apr_pool_t *scratch_pool) 2631{ 2632 const char *basename; 2633 apr_array_header_t *abspaths; 2634 int i; 2635 2636 basename = svn_relpath_basename(move->moved_from_repos_relpath, scratch_pool); 2637 SVN_ERR(svn_wc__find_working_nodes_with_basename(&abspaths, victim_abspath, 2638 basename, victim_kind, 2639 ctx->wc_ctx, result_pool, 2640 scratch_pool)); 2641 apr_array_cat(candidates, abspaths); 2642 2643 if (move->next) 2644 { 2645 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2646 for (i = 0; i < move->next->nelts; i++) 2647 { 2648 struct repos_move_info *next_move; 2649 next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); 2650 SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath, 2651 victim_kind, next_move, ctx, 2652 result_pool, iterpool)); 2653 svn_pool_clear(iterpool); 2654 } 2655 svn_pool_destroy(iterpool); 2656 } 2657 2658 return SVN_NO_ERROR; 2659} 2660 2661/* Follow each move chain starting a MOVE all the way to the end to find 2662 * the possible working copy locations for VICTIM_ABSPATH which corresponds 2663 * to VICTIM_REPOS_REPLATH@VICTIM_REVISION. 2664 * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the 2665 * repos_relpath which is the corresponding move destination in the repository. 2666 * This function is recursive. */ 2667static svn_error_t * 2668follow_move_chains(apr_hash_t *wc_move_targets, 2669 struct repos_move_info *move, 2670 svn_client_ctx_t *ctx, 2671 const char *victim_abspath, 2672 svn_node_kind_t victim_node_kind, 2673 const char *victim_repos_relpath, 2674 svn_revnum_t victim_revision, 2675 apr_pool_t *result_pool, 2676 apr_pool_t *scratch_pool) 2677{ 2678 apr_array_header_t *candidate_abspaths; 2679 2680 /* Gather candidate nodes which represent this moved_to_repos_relpath. */ 2681 SVN_ERR(svn_wc__guess_incoming_move_target_nodes( 2682 &candidate_abspaths, ctx->wc_ctx, 2683 victim_abspath, victim_node_kind, 2684 move->moved_to_repos_relpath, 2685 scratch_pool, scratch_pool)); 2686 2687 if (candidate_abspaths->nelts > 0) 2688 { 2689 apr_array_header_t *moved_to_abspaths; 2690 int i; 2691 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2692 2693 moved_to_abspaths = apr_array_make(result_pool, 1, 2694 sizeof (const char *)); 2695 2696 for (i = 0; i < candidate_abspaths->nelts; i++) 2697 { 2698 const char *candidate_abspath; 2699 const char *repos_root_url; 2700 const char *repos_uuid; 2701 const char *candidate_repos_relpath; 2702 svn_revnum_t candidate_revision; 2703 2704 svn_pool_clear(iterpool); 2705 2706 candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i, 2707 const char *); 2708 SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, 2709 &candidate_repos_relpath, 2710 &repos_root_url, 2711 &repos_uuid, 2712 NULL, NULL, 2713 ctx->wc_ctx, 2714 candidate_abspath, 2715 FALSE, 2716 iterpool, iterpool)); 2717 2718 if (candidate_revision == SVN_INVALID_REVNUM) 2719 continue; 2720 2721 /* If the conflict victim and the move target candidate 2722 * are not from the same revision we must ensure that 2723 * they are related. */ 2724 if (candidate_revision != victim_revision) 2725 { 2726 svn_client__pathrev_t *yca_loc; 2727 svn_error_t *err; 2728 2729 err = find_yca(&yca_loc, victim_repos_relpath, 2730 victim_revision, 2731 candidate_repos_relpath, 2732 candidate_revision, 2733 repos_root_url, repos_uuid, 2734 NULL, ctx, iterpool, iterpool); 2735 if (err) 2736 { 2737 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 2738 { 2739 svn_error_clear(err); 2740 yca_loc = NULL; 2741 } 2742 else 2743 return svn_error_trace(err); 2744 } 2745 2746 if (yca_loc == NULL) 2747 continue; 2748 } 2749 2750 APR_ARRAY_PUSH(moved_to_abspaths, const char *) = 2751 apr_pstrdup(result_pool, candidate_abspath); 2752 } 2753 svn_pool_destroy(iterpool); 2754 2755 svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath, 2756 moved_to_abspaths); 2757 } 2758 2759 if (move->next) 2760 { 2761 int i; 2762 apr_pool_t *iterpool; 2763 2764 /* Recurse into each of the possible move chains. */ 2765 iterpool = svn_pool_create(scratch_pool); 2766 for (i = 0; i < move->next->nelts; i++) 2767 { 2768 struct repos_move_info *next_move; 2769 2770 svn_pool_clear(iterpool); 2771 2772 next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); 2773 SVN_ERR(follow_move_chains(wc_move_targets, next_move, 2774 ctx, victim_abspath, victim_node_kind, 2775 victim_repos_relpath, victim_revision, 2776 result_pool, iterpool)); 2777 2778 } 2779 svn_pool_destroy(iterpool); 2780 } 2781 2782 return SVN_NO_ERROR; 2783} 2784 2785/* Implements tree_conflict_get_details_func_t. */ 2786static svn_error_t * 2787conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, 2788 svn_client_ctx_t *ctx, 2789 apr_pool_t *scratch_pool) 2790{ 2791 const char *old_repos_relpath; 2792 const char *new_repos_relpath; 2793 const char *parent_repos_relpath; 2794 svn_revnum_t parent_peg_rev; 2795 svn_revnum_t old_rev; 2796 svn_revnum_t new_rev; 2797 svn_revnum_t deleted_rev; 2798 svn_node_kind_t old_kind; 2799 svn_node_kind_t new_kind; 2800 const char *deleted_rev_author; 2801 svn_node_kind_t replacing_node_kind; 2802 const char *deleted_basename; 2803 struct conflict_tree_local_missing_details *details; 2804 apr_array_header_t *moves = NULL; 2805 apr_array_header_t *sibling_moves = NULL; 2806 apr_array_header_t *wc_siblings = NULL; 2807 const char *related_repos_relpath; 2808 svn_revnum_t related_peg_rev; 2809 const char *repos_root_url; 2810 const char *repos_uuid; 2811 const char *url, *corrected_url; 2812 svn_ra_session_t *ra_session; 2813 svn_client__pathrev_t *yca_loc; 2814 svn_revnum_t end_rev; 2815 2816 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 2817 &old_repos_relpath, &old_rev, &old_kind, conflict, 2818 scratch_pool, scratch_pool)); 2819 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 2820 &new_repos_relpath, &new_rev, &new_kind, conflict, 2821 scratch_pool, scratch_pool)); 2822 2823 /* Scan the conflict victim's parent's log to find a revision which 2824 * deleted the node. */ 2825 deleted_basename = svn_dirent_basename(conflict->local_abspath, 2826 scratch_pool); 2827 SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev, &parent_repos_relpath, 2828 &repos_root_url, &repos_uuid, 2829 ctx->wc_ctx, 2830 svn_dirent_dirname( 2831 conflict->local_abspath, 2832 scratch_pool), 2833 scratch_pool, 2834 scratch_pool)); 2835 2836 /* If the parent is not part of the repository-side tree checked out 2837 * into this working copy, then bail. We do not support this case yet. */ 2838 if (parent_peg_rev == SVN_INVALID_REVNUM) 2839 return SVN_NO_ERROR; 2840 2841 /* Pick the younger incoming node as our 'related node' which helps 2842 * pin-pointing the deleted conflict victim in history. */ 2843 related_repos_relpath = 2844 (old_rev < new_rev ? new_repos_relpath : old_repos_relpath); 2845 related_peg_rev = (old_rev < new_rev ? new_rev : old_rev); 2846 2847 /* Make sure we're going to search the related node in a revision where 2848 * it exists. The younger incoming node might have been deleted in HEAD. */ 2849 if (related_repos_relpath != NULL && related_peg_rev != SVN_INVALID_REVNUM) 2850 SVN_ERR(find_related_node( 2851 &related_repos_relpath, &related_peg_rev, 2852 related_repos_relpath, related_peg_rev, 2853 (old_rev < new_rev ? old_repos_relpath : new_repos_relpath), 2854 (old_rev < new_rev ? old_rev : new_rev), 2855 conflict, ctx, scratch_pool, scratch_pool)); 2856 2857 /* Set END_REV to our best guess of the nearest YCA revision. */ 2858 url = svn_path_url_add_component2(repos_root_url, related_repos_relpath, 2859 scratch_pool); 2860 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, 2861 &corrected_url, 2862 url, NULL, NULL, 2863 FALSE, 2864 FALSE, 2865 ctx, 2866 scratch_pool, 2867 scratch_pool)); 2868 SVN_ERR(find_nearest_yca(&yca_loc, related_repos_relpath, related_peg_rev, 2869 parent_repos_relpath, parent_peg_rev, 2870 repos_root_url, repos_uuid, ra_session, ctx, 2871 scratch_pool, scratch_pool)); 2872 if (yca_loc) 2873 { 2874 end_rev = yca_loc->rev; 2875 2876 /* END_REV must be smaller than PARENT_PEG_REV, else the call to 2877 * find_revision_for_suspected_deletion() below will abort. */ 2878 if (end_rev >= parent_peg_rev) 2879 end_rev = parent_peg_rev > 0 ? parent_peg_rev - 1 : 0; 2880 } 2881 else 2882 end_rev = 0; /* ### We might walk through all of history... */ 2883 2884 SVN_ERR(find_revision_for_suspected_deletion( 2885 &deleted_rev, &deleted_rev_author, &replacing_node_kind, 2886 yca_loc ? &moves : NULL, 2887 conflict, deleted_basename, parent_repos_relpath, 2888 parent_peg_rev, end_rev, related_repos_relpath, related_peg_rev, 2889 ctx, conflict->pool, scratch_pool)); 2890 2891 /* If the victim was not deleted then check if the related path was moved. */ 2892 if (deleted_rev == SVN_INVALID_REVNUM) 2893 { 2894 const char *victim_abspath; 2895 svn_node_kind_t related_node_kind; 2896 apr_array_header_t *candidates; 2897 int i; 2898 apr_pool_t *iterpool; 2899 2900 /* ### The following describes all moves in terms of forward-merges, 2901 * should do we something else for reverse-merges? */ 2902 2903 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 2904 2905 if (yca_loc) 2906 { 2907 end_rev = yca_loc->rev; 2908 2909 /* END_REV must be smaller than RELATED_PEG_REV, else the call 2910 to find_moves_in_natural_history() below will error out. */ 2911 if (end_rev >= related_peg_rev) 2912 end_rev = related_peg_rev > 0 ? related_peg_rev - 1 : 0; 2913 } 2914 else 2915 end_rev = 0; /* ### We might walk through all of history... */ 2916 2917 SVN_ERR(svn_ra_check_path(ra_session, "", related_peg_rev, 2918 &related_node_kind, scratch_pool)); 2919 SVN_ERR(find_moves_in_natural_history(&sibling_moves, 2920 related_repos_relpath, 2921 related_peg_rev, 2922 related_node_kind, 2923 end_rev, 2924 victim_abspath, 2925 repos_root_url, repos_uuid, 2926 ra_session, ctx, 2927 conflict->pool, scratch_pool)); 2928 2929 if (sibling_moves == NULL) 2930 return SVN_NO_ERROR; 2931 2932 /* Find the missing node in the WC. In theory, this requires tracing 2933 * back history of every node in the WC to check for a YCA with the 2934 * conflict victim. This operation would obviously be quite expensive. 2935 * 2936 * However, assuming that the victim was not moved in the merge target, 2937 * we can take a short-cut: The basename of the node cannot have changed, 2938 * so we can limit history tracing to nodes with a matching basename. 2939 * 2940 * This approach solves the conflict case where an edit to a file which 2941 * was moved on one branch is cherry-picked to another branch where the 2942 * corresponding file has not been moved (yet). It does not solve move 2943 * vs. move conflicts, but such conflicts are not yet supported by the 2944 * resolver anyway and are hard to solve without server-side support. */ 2945 iterpool = svn_pool_create(scratch_pool); 2946 for (i = 0; i < sibling_moves->nelts; i++) 2947 { 2948 struct repos_move_info *move; 2949 int j; 2950 2951 svn_pool_clear(iterpool); 2952 2953 move = APR_ARRAY_IDX(sibling_moves, i, struct repos_move_info *); 2954 candidates = apr_array_make(iterpool, 1, sizeof(const char *)); 2955 SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath, 2956 old_rev < new_rev 2957 ? new_kind : old_kind, 2958 move, ctx, iterpool, 2959 iterpool)); 2960 2961 /* Determine whether a candidate node shares a YCA with the victim. */ 2962 for (j = 0; j < candidates->nelts; j++) 2963 { 2964 const char *candidate_abspath; 2965 const char *candidate_repos_relpath; 2966 svn_revnum_t candidate_revision; 2967 svn_error_t *err; 2968 2969 candidate_abspath = APR_ARRAY_IDX(candidates, j, const char *); 2970 SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, 2971 &candidate_repos_relpath, 2972 NULL, NULL, NULL, NULL, 2973 ctx->wc_ctx, 2974 candidate_abspath, 2975 FALSE, 2976 iterpool, iterpool)); 2977 err = find_yca(&yca_loc, 2978 old_rev < new_rev 2979 ? new_repos_relpath : old_repos_relpath, 2980 old_rev < new_rev ? new_rev : old_rev, 2981 candidate_repos_relpath, 2982 candidate_revision, 2983 repos_root_url, repos_uuid, 2984 NULL, ctx, iterpool, iterpool); 2985 if (err) 2986 { 2987 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 2988 { 2989 svn_error_clear(err); 2990 yca_loc = NULL; 2991 } 2992 else 2993 return svn_error_trace(err); 2994 } 2995 2996 if (yca_loc) 2997 { 2998 if (wc_siblings == NULL) 2999 wc_siblings = apr_array_make(conflict->pool, 1, 3000 sizeof(const char *)); 3001 APR_ARRAY_PUSH(wc_siblings, const char *) = 3002 apr_pstrdup(conflict->pool, candidate_abspath); 3003 } 3004 } 3005 } 3006 svn_pool_destroy(iterpool); 3007 } 3008 3009 details = apr_pcalloc(conflict->pool, sizeof(*details)); 3010 details->deleted_rev = deleted_rev; 3011 details->deleted_rev_author = deleted_rev_author; 3012 if (deleted_rev != SVN_INVALID_REVNUM) 3013 details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath, 3014 deleted_basename, 3015 conflict->pool); 3016 details->moves = moves; 3017 if (details->moves != NULL) 3018 { 3019 apr_pool_t *iterpool; 3020 int i; 3021 3022 details->wc_move_targets = apr_hash_make(conflict->pool); 3023 iterpool = svn_pool_create(scratch_pool); 3024 for (i = 0; i < details->moves->nelts; i++) 3025 { 3026 struct repos_move_info *move; 3027 3028 svn_pool_clear(iterpool); 3029 move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); 3030 SVN_ERR(follow_move_chains(details->wc_move_targets, move, ctx, 3031 conflict->local_abspath, 3032 new_kind, 3033 new_repos_relpath, 3034 new_rev, 3035 scratch_pool, iterpool)); 3036 } 3037 svn_pool_destroy(iterpool); 3038 3039 if (apr_hash_count(details->wc_move_targets) > 0) 3040 { 3041 apr_array_header_t *move_target_repos_relpaths; 3042 const svn_sort__item_t *item; 3043 3044 /* Initialize to the first possible move target. Hopefully, 3045 * in most cases there will only be one candidate anyway. */ 3046 move_target_repos_relpaths = svn_sort__hash( 3047 details->wc_move_targets, 3048 svn_sort_compare_items_as_paths, 3049 scratch_pool); 3050 item = &APR_ARRAY_IDX(move_target_repos_relpaths, 3051 0, svn_sort__item_t); 3052 details->move_target_repos_relpath = item->key; 3053 details->wc_move_target_idx = 0; 3054 } 3055 else 3056 { 3057 details->move_target_repos_relpath = NULL; 3058 details->wc_move_target_idx = 0; 3059 } 3060 } 3061 3062 details->sibling_moves = sibling_moves; 3063 details->wc_siblings = wc_siblings; 3064 if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) == 1) 3065 { 3066 apr_array_header_t *wc_abspaths; 3067 3068 wc_abspaths = svn_hash_gets(details->wc_move_targets, 3069 details->move_target_repos_relpath); 3070 if (wc_abspaths->nelts == 1) 3071 { 3072 svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind; 3073 3074 if (kind == svn_node_file) 3075 conflict->recommended_option_id = 3076 svn_client_conflict_option_local_move_file_text_merge; 3077 else if (kind == svn_node_dir) 3078 conflict->recommended_option_id = 3079 svn_client_conflict_option_local_move_dir_merge; 3080 } 3081 } 3082 else if (details->wc_siblings && details->wc_siblings->nelts == 1) 3083 { 3084 svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind; 3085 3086 if (kind == svn_node_file) 3087 conflict->recommended_option_id = 3088 svn_client_conflict_option_sibling_move_file_text_merge; 3089 else if (kind == svn_node_dir) 3090 conflict->recommended_option_id = 3091 svn_client_conflict_option_sibling_move_dir_merge; 3092 } 3093 3094 conflict->tree_conflict_local_details = details; 3095 3096 return SVN_NO_ERROR; 3097} 3098 3099/* Return a localised string representation of the local part of a tree 3100 conflict on a non-existent node. */ 3101static svn_error_t * 3102describe_local_none_node_change(const char **description, 3103 svn_client_conflict_t *conflict, 3104 apr_pool_t *result_pool, 3105 apr_pool_t *scratch_pool) 3106{ 3107 svn_wc_conflict_reason_t local_change; 3108 svn_wc_operation_t operation; 3109 3110 local_change = svn_client_conflict_get_local_change(conflict); 3111 operation = svn_client_conflict_get_operation(conflict); 3112 3113 switch (local_change) 3114 { 3115 case svn_wc_conflict_reason_edited: 3116 *description = _("An item containing uncommitted changes was " 3117 "found in the working copy."); 3118 break; 3119 case svn_wc_conflict_reason_obstructed: 3120 *description = _("An item which already occupies this path was found in " 3121 "the working copy."); 3122 break; 3123 case svn_wc_conflict_reason_deleted: 3124 *description = _("A deleted item was found in the working copy."); 3125 break; 3126 case svn_wc_conflict_reason_missing: 3127 if (operation == svn_wc_operation_update || 3128 operation == svn_wc_operation_switch) 3129 *description = _("No such file or directory was found in the " 3130 "working copy."); 3131 else if (operation == svn_wc_operation_merge) 3132 { 3133 /* ### display deleted revision */ 3134 *description = _("No such file or directory was found in the " 3135 "merge target working copy.\nThe item may " 3136 "have been deleted or moved away in the " 3137 "repository's history."); 3138 } 3139 break; 3140 case svn_wc_conflict_reason_unversioned: 3141 *description = _("An unversioned item was found in the working " 3142 "copy."); 3143 break; 3144 case svn_wc_conflict_reason_added: 3145 case svn_wc_conflict_reason_replaced: 3146 *description = _("An item scheduled to be added to the repository " 3147 "in the next commit was found in the working " 3148 "copy."); 3149 break; 3150 case svn_wc_conflict_reason_moved_away: 3151 *description = _("The item in the working copy had been moved " 3152 "away at the time this conflict was recorded."); 3153 break; 3154 case svn_wc_conflict_reason_moved_here: 3155 *description = _("An item had been moved here in the working copy " 3156 "at the time this conflict was recorded."); 3157 break; 3158 } 3159 3160 return SVN_NO_ERROR; 3161} 3162 3163/* Append a description of a move chain beginning at NEXT to DESCRIPTION. */ 3164static const char * 3165append_moved_to_chain_description(const char *description, 3166 apr_array_header_t *next, 3167 apr_pool_t *result_pool, 3168 apr_pool_t *scratch_pool) 3169{ 3170 if (next == NULL) 3171 return description; 3172 3173 while (next) 3174 { 3175 struct repos_move_info *move; 3176 3177 /* Describe the first possible move chain only. Adding multiple chains 3178 * to the description would just be confusing. The user may select a 3179 * different move destination while resolving the conflict. */ 3180 move = APR_ARRAY_IDX(next, 0, struct repos_move_info *); 3181 3182 description = apr_psprintf(scratch_pool, 3183 _("%s\nAnd then moved away to '^/%s' by " 3184 "%s in r%ld."), 3185 description, move->moved_to_repos_relpath, 3186 move->rev_author, move->rev); 3187 next = move->next; 3188 } 3189 3190 return apr_pstrdup(result_pool, description); 3191} 3192 3193/* Implements tree_conflict_get_description_func_t. */ 3194static svn_error_t * 3195conflict_tree_get_local_description_generic(const char **description, 3196 svn_client_conflict_t *conflict, 3197 svn_client_ctx_t *ctx, 3198 apr_pool_t *result_pool, 3199 apr_pool_t *scratch_pool) 3200{ 3201 svn_node_kind_t victim_node_kind; 3202 3203 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 3204 3205 *description = NULL; 3206 3207 switch (victim_node_kind) 3208 { 3209 case svn_node_file: 3210 case svn_node_symlink: 3211 SVN_ERR(describe_local_file_node_change(description, conflict, ctx, 3212 result_pool, scratch_pool)); 3213 break; 3214 case svn_node_dir: 3215 SVN_ERR(describe_local_dir_node_change(description, conflict, ctx, 3216 result_pool, scratch_pool)); 3217 break; 3218 case svn_node_none: 3219 case svn_node_unknown: 3220 SVN_ERR(describe_local_none_node_change(description, conflict, 3221 result_pool, scratch_pool)); 3222 break; 3223 } 3224 3225 return SVN_NO_ERROR; 3226} 3227 3228/* Implements tree_conflict_get_description_func_t. */ 3229static svn_error_t * 3230conflict_tree_get_description_local_missing(const char **description, 3231 svn_client_conflict_t *conflict, 3232 svn_client_ctx_t *ctx, 3233 apr_pool_t *result_pool, 3234 apr_pool_t *scratch_pool) 3235{ 3236 struct conflict_tree_local_missing_details *details; 3237 3238 details = conflict->tree_conflict_local_details; 3239 if (details == NULL) 3240 return svn_error_trace(conflict_tree_get_local_description_generic( 3241 description, conflict, ctx, 3242 result_pool, scratch_pool)); 3243 3244 if (details->moves || details->sibling_moves) 3245 { 3246 struct repos_move_info *move; 3247 3248 *description = _("No such file or directory was found in the " 3249 "merge target working copy.\n"); 3250 3251 if (details->moves) 3252 { 3253 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3254 if (move->node_kind == svn_node_file) 3255 *description = apr_psprintf( 3256 result_pool, 3257 _("%sThe file was moved to '^/%s' in r%ld by %s."), 3258 *description, move->moved_to_repos_relpath, 3259 move->rev, move->rev_author); 3260 else if (move->node_kind == svn_node_dir) 3261 *description = apr_psprintf( 3262 result_pool, 3263 _("%sThe directory was moved to '^/%s' in " 3264 "r%ld by %s."), 3265 *description, move->moved_to_repos_relpath, 3266 move->rev, move->rev_author); 3267 else 3268 *description = apr_psprintf( 3269 result_pool, 3270 _("%sThe item was moved to '^/%s' in r%ld by %s."), 3271 *description, move->moved_to_repos_relpath, 3272 move->rev, move->rev_author); 3273 *description = append_moved_to_chain_description(*description, 3274 move->next, 3275 result_pool, 3276 scratch_pool); 3277 } 3278 3279 if (details->sibling_moves) 3280 { 3281 move = APR_ARRAY_IDX(details->sibling_moves, 0, 3282 struct repos_move_info *); 3283 if (move->node_kind == svn_node_file) 3284 *description = apr_psprintf( 3285 result_pool, 3286 _("%sThe file '^/%s' was moved to '^/%s' " 3287 "in r%ld by %s."), 3288 *description, move->moved_from_repos_relpath, 3289 move->moved_to_repos_relpath, 3290 move->rev, move->rev_author); 3291 else if (move->node_kind == svn_node_dir) 3292 *description = apr_psprintf( 3293 result_pool, 3294 _("%sThe directory '^/%s' was moved to '^/%s' " 3295 "in r%ld by %s."), 3296 *description, move->moved_from_repos_relpath, 3297 move->moved_to_repos_relpath, 3298 move->rev, move->rev_author); 3299 else 3300 *description = apr_psprintf( 3301 result_pool, 3302 _("%sThe item '^/%s' was moved to '^/%s' " 3303 "in r%ld by %s."), 3304 *description, move->moved_from_repos_relpath, 3305 move->moved_to_repos_relpath, 3306 move->rev, move->rev_author); 3307 *description = append_moved_to_chain_description(*description, 3308 move->next, 3309 result_pool, 3310 scratch_pool); 3311 } 3312 } 3313 else 3314 *description = apr_psprintf( 3315 result_pool, 3316 _("No such file or directory was found in the " 3317 "merge target working copy.\n'^/%s' was deleted " 3318 "in r%ld by %s."), 3319 details->deleted_repos_relpath, 3320 details->deleted_rev, details->deleted_rev_author); 3321 3322 return SVN_NO_ERROR; 3323} 3324 3325/* Return a localised string representation of the incoming part of a 3326 conflict; NULL for non-localised odd cases. */ 3327static const char * 3328describe_incoming_change(svn_node_kind_t kind, svn_wc_conflict_action_t action, 3329 svn_wc_operation_t operation) 3330{ 3331 switch (kind) 3332 { 3333 case svn_node_file: 3334 case svn_node_symlink: 3335 if (operation == svn_wc_operation_update) 3336 { 3337 switch (action) 3338 { 3339 case svn_wc_conflict_action_edit: 3340 return _("An update operation tried to edit a file."); 3341 case svn_wc_conflict_action_add: 3342 return _("An update operation tried to add a file."); 3343 case svn_wc_conflict_action_delete: 3344 return _("An update operation tried to delete or move " 3345 "a file."); 3346 case svn_wc_conflict_action_replace: 3347 return _("An update operation tried to replace a file."); 3348 } 3349 } 3350 else if (operation == svn_wc_operation_switch) 3351 { 3352 switch (action) 3353 { 3354 case svn_wc_conflict_action_edit: 3355 return _("A switch operation tried to edit a file."); 3356 case svn_wc_conflict_action_add: 3357 return _("A switch operation tried to add a file."); 3358 case svn_wc_conflict_action_delete: 3359 return _("A switch operation tried to delete or move " 3360 "a file."); 3361 case svn_wc_conflict_action_replace: 3362 return _("A switch operation tried to replace a file."); 3363 } 3364 } 3365 else if (operation == svn_wc_operation_merge) 3366 { 3367 switch (action) 3368 { 3369 case svn_wc_conflict_action_edit: 3370 return _("A merge operation tried to edit a file."); 3371 case svn_wc_conflict_action_add: 3372 return _("A merge operation tried to add a file."); 3373 case svn_wc_conflict_action_delete: 3374 return _("A merge operation tried to delete or move " 3375 "a file."); 3376 case svn_wc_conflict_action_replace: 3377 return _("A merge operation tried to replace a file."); 3378 } 3379 } 3380 break; 3381 case svn_node_dir: 3382 if (operation == svn_wc_operation_update) 3383 { 3384 switch (action) 3385 { 3386 case svn_wc_conflict_action_edit: 3387 return _("An update operation tried to change a directory."); 3388 case svn_wc_conflict_action_add: 3389 return _("An update operation tried to add a directory."); 3390 case svn_wc_conflict_action_delete: 3391 return _("An update operation tried to delete or move " 3392 "a directory."); 3393 case svn_wc_conflict_action_replace: 3394 return _("An update operation tried to replace a directory."); 3395 } 3396 } 3397 else if (operation == svn_wc_operation_switch) 3398 { 3399 switch (action) 3400 { 3401 case svn_wc_conflict_action_edit: 3402 return _("A switch operation tried to edit a directory."); 3403 case svn_wc_conflict_action_add: 3404 return _("A switch operation tried to add a directory."); 3405 case svn_wc_conflict_action_delete: 3406 return _("A switch operation tried to delete or move " 3407 "a directory."); 3408 case svn_wc_conflict_action_replace: 3409 return _("A switch operation tried to replace a directory."); 3410 } 3411 } 3412 else if (operation == svn_wc_operation_merge) 3413 { 3414 switch (action) 3415 { 3416 case svn_wc_conflict_action_edit: 3417 return _("A merge operation tried to edit a directory."); 3418 case svn_wc_conflict_action_add: 3419 return _("A merge operation tried to add a directory."); 3420 case svn_wc_conflict_action_delete: 3421 return _("A merge operation tried to delete or move " 3422 "a directory."); 3423 case svn_wc_conflict_action_replace: 3424 return _("A merge operation tried to replace a directory."); 3425 } 3426 } 3427 break; 3428 case svn_node_none: 3429 case svn_node_unknown: 3430 if (operation == svn_wc_operation_update) 3431 { 3432 switch (action) 3433 { 3434 case svn_wc_conflict_action_edit: 3435 return _("An update operation tried to edit an item."); 3436 case svn_wc_conflict_action_add: 3437 return _("An update operation tried to add an item."); 3438 case svn_wc_conflict_action_delete: 3439 return _("An update operation tried to delete or move " 3440 "an item."); 3441 case svn_wc_conflict_action_replace: 3442 return _("An update operation tried to replace an item."); 3443 } 3444 } 3445 else if (operation == svn_wc_operation_switch) 3446 { 3447 switch (action) 3448 { 3449 case svn_wc_conflict_action_edit: 3450 return _("A switch operation tried to edit an item."); 3451 case svn_wc_conflict_action_add: 3452 return _("A switch operation tried to add an item."); 3453 case svn_wc_conflict_action_delete: 3454 return _("A switch operation tried to delete or move " 3455 "an item."); 3456 case svn_wc_conflict_action_replace: 3457 return _("A switch operation tried to replace an item."); 3458 } 3459 } 3460 else if (operation == svn_wc_operation_merge) 3461 { 3462 switch (action) 3463 { 3464 case svn_wc_conflict_action_edit: 3465 return _("A merge operation tried to edit an item."); 3466 case svn_wc_conflict_action_add: 3467 return _("A merge operation tried to add an item."); 3468 case svn_wc_conflict_action_delete: 3469 return _("A merge operation tried to delete or move " 3470 "an item."); 3471 case svn_wc_conflict_action_replace: 3472 return _("A merge operation tried to replace an item."); 3473 } 3474 } 3475 break; 3476 } 3477 3478 return NULL; 3479} 3480 3481/* Return a localised string representation of the operation part of a 3482 conflict. */ 3483static const char * 3484operation_str(svn_wc_operation_t operation) 3485{ 3486 switch (operation) 3487 { 3488 case svn_wc_operation_update: return _("upon update"); 3489 case svn_wc_operation_switch: return _("upon switch"); 3490 case svn_wc_operation_merge: return _("upon merge"); 3491 case svn_wc_operation_none: return _("upon none"); 3492 } 3493 SVN_ERR_MALFUNCTION_NO_RETURN(); 3494 return NULL; 3495} 3496 3497svn_error_t * 3498svn_client_conflict_prop_get_description(const char **description, 3499 svn_client_conflict_t *conflict, 3500 apr_pool_t *result_pool, 3501 apr_pool_t *scratch_pool) 3502{ 3503 const char *reason_str, *action_str; 3504 3505 /* We provide separately translatable strings for the values that we 3506 * know about, and a fall-back in case any other values occur. */ 3507 switch (svn_client_conflict_get_local_change(conflict)) 3508 { 3509 case svn_wc_conflict_reason_edited: 3510 reason_str = _("local edit"); 3511 break; 3512 case svn_wc_conflict_reason_added: 3513 reason_str = _("local add"); 3514 break; 3515 case svn_wc_conflict_reason_deleted: 3516 reason_str = _("local delete"); 3517 break; 3518 case svn_wc_conflict_reason_obstructed: 3519 reason_str = _("local obstruction"); 3520 break; 3521 default: 3522 reason_str = apr_psprintf( 3523 scratch_pool, _("local %s"), 3524 svn_token__to_word( 3525 map_conflict_reason, 3526 svn_client_conflict_get_local_change(conflict))); 3527 break; 3528 } 3529 switch (svn_client_conflict_get_incoming_change(conflict)) 3530 { 3531 case svn_wc_conflict_action_edit: 3532 action_str = _("incoming edit"); 3533 break; 3534 case svn_wc_conflict_action_add: 3535 action_str = _("incoming add"); 3536 break; 3537 case svn_wc_conflict_action_delete: 3538 action_str = _("incoming delete"); 3539 break; 3540 default: 3541 action_str = apr_psprintf( 3542 scratch_pool, _("incoming %s"), 3543 svn_token__to_word( 3544 map_conflict_action, 3545 svn_client_conflict_get_incoming_change(conflict))); 3546 break; 3547 } 3548 SVN_ERR_ASSERT(reason_str && action_str); 3549 3550 *description = apr_psprintf(result_pool, _("%s, %s %s"), 3551 reason_str, action_str, 3552 operation_str( 3553 svn_client_conflict_get_operation(conflict))); 3554 3555 return SVN_NO_ERROR; 3556} 3557 3558/* Implements tree_conflict_get_description_func_t. */ 3559static svn_error_t * 3560conflict_tree_get_incoming_description_generic( 3561 const char **incoming_change_description, 3562 svn_client_conflict_t *conflict, 3563 svn_client_ctx_t *ctx, 3564 apr_pool_t *result_pool, 3565 apr_pool_t *scratch_pool) 3566{ 3567 const char *action; 3568 svn_node_kind_t incoming_kind; 3569 svn_wc_conflict_action_t conflict_action; 3570 svn_wc_operation_t conflict_operation; 3571 3572 conflict_action = svn_client_conflict_get_incoming_change(conflict); 3573 conflict_operation = svn_client_conflict_get_operation(conflict); 3574 3575 /* Determine the node kind of the incoming change. */ 3576 incoming_kind = svn_node_unknown; 3577 if (conflict_action == svn_wc_conflict_action_edit || 3578 conflict_action == svn_wc_conflict_action_delete) 3579 { 3580 /* Change is acting on 'src_left' version of the node. */ 3581 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 3582 NULL, NULL, &incoming_kind, conflict, scratch_pool, 3583 scratch_pool)); 3584 } 3585 else if (conflict_action == svn_wc_conflict_action_add || 3586 conflict_action == svn_wc_conflict_action_replace) 3587 { 3588 /* Change is acting on 'src_right' version of the node. 3589 * 3590 * ### For 'replace', the node kind is ambiguous. However, src_left 3591 * ### is NULL for replace, so we must use src_right. */ 3592 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 3593 NULL, NULL, &incoming_kind, conflict, scratch_pool, 3594 scratch_pool)); 3595 } 3596 3597 action = describe_incoming_change(incoming_kind, conflict_action, 3598 conflict_operation); 3599 if (action) 3600 { 3601 *incoming_change_description = apr_pstrdup(result_pool, action); 3602 } 3603 else 3604 { 3605 /* A catch-all message for very rare or nominally impossible cases. 3606 It will not be pretty, but is closer to an internal error than 3607 an ordinary user-facing string. */ 3608 *incoming_change_description = apr_psprintf(result_pool, 3609 _("incoming %s %s"), 3610 svn_node_kind_to_word(incoming_kind), 3611 svn_token__to_word(map_conflict_action, 3612 conflict_action)); 3613 } 3614 return SVN_NO_ERROR; 3615} 3616 3617/* Details for tree conflicts involving incoming deletions and replacements. */ 3618struct conflict_tree_incoming_delete_details 3619{ 3620 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */ 3621 svn_revnum_t deleted_rev; 3622 3623 /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. The incoming 3624 * delete is the result of a reverse application of this addition. */ 3625 svn_revnum_t added_rev; 3626 3627 /* The path which was deleted/added relative to the repository root. */ 3628 const char *repos_relpath; 3629 3630 /* Author who committed DELETED_REV/ADDED_REV. */ 3631 const char *rev_author; 3632 3633 /* New node kind for a replaced node. This is svn_node_none for deletions. */ 3634 svn_node_kind_t replacing_node_kind; 3635 3636 /* Move information. If not NULL, this is an array of repos_move_info * 3637 * elements. Each element is the head of a move chain which starts in 3638 * DELETED_REV or in ADDED_REV (in which case moves should be interpreted 3639 * in reverse). */ 3640 apr_array_header_t *moves; 3641 3642 /* A map of repos_relpaths and working copy nodes for an incoming move. 3643 * 3644 * Each key is a "const char *" repository relpath corresponding to a 3645 * possible repository-side move destination node in the revision which 3646 * is the target revision in case of update and switch, or the merge-right 3647 * revision in case of a merge. 3648 * 3649 * Each value is an apr_array_header_t *. 3650 * Each array consists of "const char *" absolute paths to working copy 3651 * nodes which correspond to the repository node selected by the map key. 3652 * Each such working copy node is a potential local move target which can 3653 * be chosen to "follow" the incoming move when resolving a tree conflict. 3654 * 3655 * This may be an empty hash map in case if there is no move target path 3656 * in the working copy. */ 3657 apr_hash_t *wc_move_targets; 3658 3659 /* The preferred move target repository relpath. This is our key into 3660 * the WC_MOVE_TARGETS map above (can be overridden by the user). */ 3661 const char *move_target_repos_relpath; 3662 3663 /* The current index into the list of working copy nodes corresponding to 3664 * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */ 3665 int wc_move_target_idx; 3666}; 3667 3668/* Get the currently selected repository-side move target path. 3669 * If none was selected yet, determine and return a default one. */ 3670static const char * 3671get_moved_to_repos_relpath( 3672 struct conflict_tree_incoming_delete_details *details, 3673 apr_pool_t *scratch_pool) 3674{ 3675 struct repos_move_info *move; 3676 3677 if (details->move_target_repos_relpath) 3678 return details->move_target_repos_relpath; 3679 3680 if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) > 0) 3681 { 3682 svn_sort__item_t item; 3683 apr_array_header_t *repos_relpaths; 3684 3685 repos_relpaths = svn_sort__hash(details->wc_move_targets, 3686 svn_sort_compare_items_as_paths, 3687 scratch_pool); 3688 item = APR_ARRAY_IDX(repos_relpaths, 0, svn_sort__item_t); 3689 return (const char *)item.key; 3690 } 3691 3692 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3693 return move->moved_to_repos_relpath; 3694} 3695 3696static const char * 3697describe_incoming_deletion_upon_update( 3698 struct conflict_tree_incoming_delete_details *details, 3699 svn_node_kind_t victim_node_kind, 3700 svn_revnum_t old_rev, 3701 svn_revnum_t new_rev, 3702 apr_pool_t *result_pool, 3703 apr_pool_t *scratch_pool) 3704{ 3705 if (details->replacing_node_kind == svn_node_file || 3706 details->replacing_node_kind == svn_node_symlink) 3707 { 3708 if (victim_node_kind == svn_node_dir) 3709 { 3710 const char *description = 3711 apr_psprintf(result_pool, 3712 _("Directory updated from r%ld to r%ld was " 3713 "replaced with a file by %s in r%ld."), 3714 old_rev, new_rev, 3715 details->rev_author, details->deleted_rev); 3716 if (details->moves) 3717 { 3718 struct repos_move_info *move; 3719 3720 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3721 description = 3722 apr_psprintf(result_pool, 3723 _("%s\nThe replaced directory was moved to " 3724 "'^/%s'."), description, 3725 get_moved_to_repos_relpath(details, scratch_pool)); 3726 return append_moved_to_chain_description(description, 3727 move->next, 3728 result_pool, 3729 scratch_pool); 3730 } 3731 return description; 3732 } 3733 else if (victim_node_kind == svn_node_file || 3734 victim_node_kind == svn_node_symlink) 3735 { 3736 const char *description = 3737 apr_psprintf(result_pool, 3738 _("File updated from r%ld to r%ld was replaced " 3739 "with a file from another line of history by " 3740 "%s in r%ld."), 3741 old_rev, new_rev, 3742 details->rev_author, details->deleted_rev); 3743 if (details->moves) 3744 { 3745 struct repos_move_info *move; 3746 3747 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3748 description = 3749 apr_psprintf(result_pool, 3750 _("%s\nThe replaced file was moved to '^/%s'."), 3751 description, 3752 get_moved_to_repos_relpath(details, scratch_pool)); 3753 return append_moved_to_chain_description(description, 3754 move->next, 3755 result_pool, 3756 scratch_pool); 3757 } 3758 return description; 3759 } 3760 else 3761 { 3762 const char *description = 3763 apr_psprintf(result_pool, 3764 _("Item updated from r%ld to r%ld was replaced " 3765 "with a file by %s in r%ld."), old_rev, new_rev, 3766 details->rev_author, details->deleted_rev); 3767 if (details->moves) 3768 { 3769 struct repos_move_info *move; 3770 3771 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3772 description = 3773 apr_psprintf(result_pool, 3774 _("%s\nThe replaced item was moved to '^/%s'."), 3775 description, 3776 get_moved_to_repos_relpath(details, scratch_pool)); 3777 return append_moved_to_chain_description(description, 3778 move->next, 3779 result_pool, 3780 scratch_pool); 3781 } 3782 return description; 3783 } 3784 } 3785 else if (details->replacing_node_kind == svn_node_dir) 3786 { 3787 if (victim_node_kind == svn_node_dir) 3788 { 3789 const char *description = 3790 apr_psprintf(result_pool, 3791 _("Directory updated from r%ld to r%ld was " 3792 "replaced with a directory from another line " 3793 "of history by %s in r%ld."), 3794 old_rev, new_rev, 3795 details->rev_author, details->deleted_rev); 3796 if (details->moves) 3797 { 3798 struct repos_move_info *move; 3799 3800 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3801 description = 3802 apr_psprintf(result_pool, 3803 _("%s\nThe replaced directory was moved to " 3804 "'^/%s'."), description, 3805 get_moved_to_repos_relpath(details, scratch_pool)); 3806 return append_moved_to_chain_description(description, 3807 move->next, 3808 result_pool, 3809 scratch_pool); 3810 } 3811 return description; 3812 } 3813 else if (victim_node_kind == svn_node_file || 3814 victim_node_kind == svn_node_symlink) 3815 { 3816 const char *description = 3817 apr_psprintf(result_pool, 3818 _("File updated from r%ld to r%ld was " 3819 "replaced with a directory by %s in r%ld."), 3820 old_rev, new_rev, 3821 details->rev_author, details->deleted_rev); 3822 if (details->moves) 3823 { 3824 struct repos_move_info *move; 3825 3826 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3827 description = 3828 apr_psprintf(result_pool, 3829 _("%s\nThe replaced file was moved to '^/%s'."), 3830 description, 3831 get_moved_to_repos_relpath(details, scratch_pool)); 3832 return append_moved_to_chain_description(description, 3833 move->next, 3834 result_pool, 3835 scratch_pool); 3836 } 3837 return description; 3838 } 3839 else 3840 { 3841 const char *description = 3842 apr_psprintf(result_pool, 3843 _("Item updated from r%ld to r%ld was replaced " 3844 "by %s in r%ld."), old_rev, new_rev, 3845 details->rev_author, details->deleted_rev); 3846 if (details->moves) 3847 { 3848 struct repos_move_info *move; 3849 3850 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3851 description = 3852 apr_psprintf(result_pool, 3853 _("%s\nThe replaced item was moved to '^/%s'."), 3854 description, 3855 get_moved_to_repos_relpath(details, scratch_pool)); 3856 return append_moved_to_chain_description(description, 3857 move->next, 3858 result_pool, 3859 scratch_pool); 3860 } 3861 return description; 3862 } 3863 } 3864 else 3865 { 3866 if (victim_node_kind == svn_node_dir) 3867 { 3868 if (details->moves) 3869 { 3870 const char *description; 3871 struct repos_move_info *move; 3872 3873 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3874 description = 3875 apr_psprintf(result_pool, 3876 _("Directory updated from r%ld to r%ld was " 3877 "moved to '^/%s' by %s in r%ld."), 3878 old_rev, new_rev, 3879 get_moved_to_repos_relpath(details, scratch_pool), 3880 details->rev_author, details->deleted_rev); 3881 return append_moved_to_chain_description(description, 3882 move->next, 3883 result_pool, 3884 scratch_pool); 3885 } 3886 else 3887 return apr_psprintf(result_pool, 3888 _("Directory updated from r%ld to r%ld was " 3889 "deleted by %s in r%ld."), 3890 old_rev, new_rev, 3891 details->rev_author, details->deleted_rev); 3892 } 3893 else if (victim_node_kind == svn_node_file || 3894 victim_node_kind == svn_node_symlink) 3895 { 3896 if (details->moves) 3897 { 3898 struct repos_move_info *move; 3899 const char *description; 3900 3901 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3902 description = 3903 apr_psprintf(result_pool, 3904 _("File updated from r%ld to r%ld was moved " 3905 "to '^/%s' by %s in r%ld."), old_rev, new_rev, 3906 get_moved_to_repos_relpath(details, scratch_pool), 3907 details->rev_author, details->deleted_rev); 3908 return append_moved_to_chain_description(description, 3909 move->next, 3910 result_pool, 3911 scratch_pool); 3912 } 3913 else 3914 return apr_psprintf(result_pool, 3915 _("File updated from r%ld to r%ld was " 3916 "deleted by %s in r%ld."), old_rev, new_rev, 3917 details->rev_author, details->deleted_rev); 3918 } 3919 else 3920 { 3921 if (details->moves) 3922 { 3923 const char *description; 3924 struct repos_move_info *move; 3925 3926 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 3927 description = 3928 apr_psprintf(result_pool, 3929 _("Item updated from r%ld to r%ld was moved " 3930 "to '^/%s' by %s in r%ld."), old_rev, new_rev, 3931 get_moved_to_repos_relpath(details, scratch_pool), 3932 details->rev_author, details->deleted_rev); 3933 return append_moved_to_chain_description(description, 3934 move->next, 3935 result_pool, 3936 scratch_pool); 3937 } 3938 else 3939 return apr_psprintf(result_pool, 3940 _("Item updated from r%ld to r%ld was " 3941 "deleted by %s in r%ld."), old_rev, new_rev, 3942 details->rev_author, details->deleted_rev); 3943 } 3944 } 3945} 3946 3947static const char * 3948describe_incoming_reverse_addition_upon_update( 3949 struct conflict_tree_incoming_delete_details *details, 3950 svn_node_kind_t victim_node_kind, 3951 svn_revnum_t old_rev, 3952 svn_revnum_t new_rev, 3953 apr_pool_t *result_pool) 3954{ 3955 if (details->replacing_node_kind == svn_node_file || 3956 details->replacing_node_kind == svn_node_symlink) 3957 { 3958 if (victim_node_kind == svn_node_dir) 3959 return apr_psprintf(result_pool, 3960 _("Directory updated backwards from r%ld to r%ld " 3961 "was a file before the replacement made by %s " 3962 "in r%ld."), old_rev, new_rev, 3963 details->rev_author, details->added_rev); 3964 else if (victim_node_kind == svn_node_file || 3965 victim_node_kind == svn_node_symlink) 3966 return apr_psprintf(result_pool, 3967 _("File updated backwards from r%ld to r%ld was a " 3968 "file from another line of history before the " 3969 "replacement made by %s in r%ld."), 3970 old_rev, new_rev, 3971 details->rev_author, details->added_rev); 3972 else 3973 return apr_psprintf(result_pool, 3974 _("Item updated backwards from r%ld to r%ld was " 3975 "replaced with a file by %s in r%ld."), 3976 old_rev, new_rev, 3977 details->rev_author, details->added_rev); 3978 } 3979 else if (details->replacing_node_kind == svn_node_dir) 3980 { 3981 if (victim_node_kind == svn_node_dir) 3982 return apr_psprintf(result_pool, 3983 _("Directory updated backwards from r%ld to r%ld " 3984 "was a directory from another line of history " 3985 "before the replacement made by %s in " 3986 "r%ld."), old_rev, new_rev, 3987 details->rev_author, details->added_rev); 3988 else if (victim_node_kind == svn_node_file || 3989 victim_node_kind == svn_node_symlink) 3990 return apr_psprintf(result_pool, 3991 _("File updated backwards from r%ld to r%ld was a " 3992 "directory before the replacement made by %s " 3993 "in r%ld."), old_rev, new_rev, 3994 details->rev_author, details->added_rev); 3995 else 3996 return apr_psprintf(result_pool, 3997 _("Item updated backwards from r%ld to r%ld was " 3998 "replaced with a directory by %s in r%ld."), 3999 old_rev, new_rev, 4000 details->rev_author, details->added_rev); 4001 } 4002 else 4003 { 4004 if (victim_node_kind == svn_node_dir) 4005 return apr_psprintf(result_pool, 4006 _("Directory updated backwards from r%ld to r%ld " 4007 "did not exist before it was added by %s in " 4008 "r%ld."), old_rev, new_rev, 4009 details->rev_author, details->added_rev); 4010 else if (victim_node_kind == svn_node_file || 4011 victim_node_kind == svn_node_symlink) 4012 return apr_psprintf(result_pool, 4013 _("File updated backwards from r%ld to r%ld did " 4014 "not exist before it was added by %s in r%ld."), 4015 old_rev, new_rev, 4016 details->rev_author, details->added_rev); 4017 else 4018 return apr_psprintf(result_pool, 4019 _("Item updated backwards from r%ld to r%ld did " 4020 "not exist before it was added by %s in r%ld."), 4021 old_rev, new_rev, 4022 details->rev_author, details->added_rev); 4023 } 4024} 4025 4026static const char * 4027describe_incoming_deletion_upon_switch( 4028 struct conflict_tree_incoming_delete_details *details, 4029 svn_node_kind_t victim_node_kind, 4030 const char *old_repos_relpath, 4031 svn_revnum_t old_rev, 4032 const char *new_repos_relpath, 4033 svn_revnum_t new_rev, 4034 apr_pool_t *result_pool, 4035 apr_pool_t *scratch_pool) 4036{ 4037 if (details->replacing_node_kind == svn_node_file || 4038 details->replacing_node_kind == svn_node_symlink) 4039 { 4040 if (victim_node_kind == svn_node_dir) 4041 { 4042 const char *description = 4043 apr_psprintf(result_pool, 4044 _("Directory switched from\n" 4045 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4046 "was replaced with a file by %s in r%ld."), 4047 old_repos_relpath, old_rev, 4048 new_repos_relpath, new_rev, 4049 details->rev_author, details->deleted_rev); 4050 if (details->moves) 4051 { 4052 struct repos_move_info *move; 4053 4054 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4055 description = 4056 apr_psprintf(result_pool, 4057 _("%s\nThe replaced directory was moved " 4058 "to '^/%s'."), description, 4059 get_moved_to_repos_relpath(details, scratch_pool)); 4060 return append_moved_to_chain_description(description, 4061 move->next, 4062 result_pool, 4063 scratch_pool); 4064 } 4065 return description; 4066 } 4067 else if (victim_node_kind == svn_node_file || 4068 victim_node_kind == svn_node_symlink) 4069 { 4070 const char *description = 4071 apr_psprintf(result_pool, 4072 _("File switched from\n" 4073 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4074 "replaced with a file from another line of " 4075 "history by %s in r%ld."), 4076 old_repos_relpath, old_rev, 4077 new_repos_relpath, new_rev, 4078 details->rev_author, details->deleted_rev); 4079 if (details->moves) 4080 { 4081 struct repos_move_info *move; 4082 4083 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4084 description = 4085 apr_psprintf(result_pool, 4086 _("%s\nThe replaced file was moved to '^/%s'."), 4087 description, 4088 get_moved_to_repos_relpath(details, scratch_pool)); 4089 return append_moved_to_chain_description(description, 4090 move->next, 4091 result_pool, 4092 scratch_pool); 4093 } 4094 return description; 4095 } 4096 else 4097 { 4098 const char *description = 4099 apr_psprintf(result_pool, 4100 _("Item switched from\n" 4101 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4102 "replaced with a file by %s in r%ld."), 4103 old_repos_relpath, old_rev, 4104 new_repos_relpath, new_rev, 4105 details->rev_author, details->deleted_rev); 4106 if (details->moves) 4107 { 4108 struct repos_move_info *move; 4109 4110 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4111 description = 4112 apr_psprintf(result_pool, 4113 _("%s\nThe replaced item was moved to '^/%s'."), 4114 description, 4115 get_moved_to_repos_relpath(details, scratch_pool)); 4116 return append_moved_to_chain_description(description, 4117 move->next, 4118 result_pool, 4119 scratch_pool); 4120 } 4121 return description; 4122 } 4123 } 4124 else if (details->replacing_node_kind == svn_node_dir) 4125 { 4126 if (victim_node_kind == svn_node_dir) 4127 { 4128 const char *description = 4129 apr_psprintf(result_pool, 4130 _("Directory switched from\n" 4131 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4132 "was replaced with a directory from another " 4133 "line of history by %s in r%ld."), 4134 old_repos_relpath, old_rev, 4135 new_repos_relpath, new_rev, 4136 details->rev_author, details->deleted_rev); 4137 if (details->moves) 4138 { 4139 struct repos_move_info *move; 4140 4141 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4142 description = 4143 apr_psprintf(result_pool, 4144 _("%s\nThe replaced directory was moved to " 4145 "'^/%s'."), description, 4146 get_moved_to_repos_relpath(details, scratch_pool)); 4147 return append_moved_to_chain_description(description, 4148 move->next, 4149 result_pool, 4150 scratch_pool); 4151 } 4152 return description; 4153 } 4154 else if (victim_node_kind == svn_node_file || 4155 victim_node_kind == svn_node_symlink) 4156 { 4157 const char *description = 4158 apr_psprintf(result_pool, 4159 _("File switched from\n" 4160 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4161 "was replaced with a directory by %s in r%ld."), 4162 old_repos_relpath, old_rev, 4163 new_repos_relpath, new_rev, 4164 details->rev_author, details->deleted_rev); 4165 if (details->moves) 4166 { 4167 struct repos_move_info *move; 4168 4169 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4170 description = 4171 apr_psprintf(result_pool, 4172 _("%s\nThe replaced file was moved to '^/%s'."), 4173 description, 4174 get_moved_to_repos_relpath(details, scratch_pool)); 4175 return append_moved_to_chain_description(description, 4176 move->next, 4177 result_pool, 4178 scratch_pool); 4179 } 4180 return description; 4181 } 4182 else 4183 { 4184 const char *description = 4185 apr_psprintf(result_pool, 4186 _("Item switched from\n" 4187 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4188 "replaced with a directory by %s in r%ld."), 4189 old_repos_relpath, old_rev, 4190 new_repos_relpath, new_rev, 4191 details->rev_author, details->deleted_rev); 4192 if (details->moves) 4193 { 4194 struct repos_move_info *move; 4195 4196 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4197 description = 4198 apr_psprintf(result_pool, 4199 _("%s\nThe replaced item was moved to '^/%s'."), 4200 description, 4201 get_moved_to_repos_relpath(details, scratch_pool)); 4202 return append_moved_to_chain_description(description, 4203 move->next, 4204 result_pool, 4205 scratch_pool); 4206 } 4207 return description; 4208 } 4209 } 4210 else 4211 { 4212 if (victim_node_kind == svn_node_dir) 4213 { 4214 if (details->moves) 4215 { 4216 struct repos_move_info *move; 4217 const char *description; 4218 4219 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4220 description = 4221 apr_psprintf(result_pool, 4222 _("Directory switched from\n" 4223 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4224 "was moved to '^/%s' by %s in r%ld."), 4225 old_repos_relpath, old_rev, 4226 new_repos_relpath, new_rev, 4227 get_moved_to_repos_relpath(details, scratch_pool), 4228 details->rev_author, details->deleted_rev); 4229 return append_moved_to_chain_description(description, 4230 move->next, 4231 result_pool, 4232 scratch_pool); 4233 } 4234 else 4235 return apr_psprintf(result_pool, 4236 _("Directory switched from\n" 4237 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4238 "was deleted by %s in r%ld."), 4239 old_repos_relpath, old_rev, 4240 new_repos_relpath, new_rev, 4241 details->rev_author, details->deleted_rev); 4242 } 4243 else if (victim_node_kind == svn_node_file || 4244 victim_node_kind == svn_node_symlink) 4245 { 4246 if (details->moves) 4247 { 4248 struct repos_move_info *move; 4249 const char *description; 4250 4251 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4252 description = 4253 apr_psprintf(result_pool, 4254 _("File switched from\n" 4255 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4256 "moved to '^/%s' by %s in r%ld."), 4257 old_repos_relpath, old_rev, 4258 new_repos_relpath, new_rev, 4259 get_moved_to_repos_relpath(details, scratch_pool), 4260 details->rev_author, details->deleted_rev); 4261 return append_moved_to_chain_description(description, 4262 move->next, 4263 result_pool, 4264 scratch_pool); 4265 } 4266 else 4267 return apr_psprintf(result_pool, 4268 _("File switched from\n" 4269 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4270 "deleted by %s in r%ld."), 4271 old_repos_relpath, old_rev, 4272 new_repos_relpath, new_rev, 4273 details->rev_author, details->deleted_rev); 4274 } 4275 else 4276 { 4277 if (details->moves) 4278 { 4279 struct repos_move_info *move; 4280 const char *description; 4281 4282 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4283 description = 4284 apr_psprintf(result_pool, 4285 _("Item switched from\n" 4286 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4287 "moved to '^/%s' by %s in r%ld."), 4288 old_repos_relpath, old_rev, 4289 new_repos_relpath, new_rev, 4290 get_moved_to_repos_relpath(details, scratch_pool), 4291 details->rev_author, details->deleted_rev); 4292 return append_moved_to_chain_description(description, 4293 move->next, 4294 result_pool, 4295 scratch_pool); 4296 } 4297 else 4298 return apr_psprintf(result_pool, 4299 _("Item switched from\n" 4300 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4301 "deleted by %s in r%ld."), 4302 old_repos_relpath, old_rev, 4303 new_repos_relpath, new_rev, 4304 details->rev_author, details->deleted_rev); 4305 } 4306 } 4307} 4308 4309static const char * 4310describe_incoming_reverse_addition_upon_switch( 4311 struct conflict_tree_incoming_delete_details *details, 4312 svn_node_kind_t victim_node_kind, 4313 const char *old_repos_relpath, 4314 svn_revnum_t old_rev, 4315 const char *new_repos_relpath, 4316 svn_revnum_t new_rev, 4317 apr_pool_t *result_pool) 4318{ 4319 if (details->replacing_node_kind == svn_node_file || 4320 details->replacing_node_kind == svn_node_symlink) 4321 { 4322 if (victim_node_kind == svn_node_dir) 4323 return apr_psprintf(result_pool, 4324 _("Directory switched from\n" 4325 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4326 "was a file before the replacement made by %s " 4327 "in r%ld."), 4328 old_repos_relpath, old_rev, 4329 new_repos_relpath, new_rev, 4330 details->rev_author, details->added_rev); 4331 else if (victim_node_kind == svn_node_file || 4332 victim_node_kind == svn_node_symlink) 4333 return apr_psprintf(result_pool, 4334 _("File switched from\n" 4335 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas a " 4336 "file from another line of history before the " 4337 "replacement made by %s in r%ld."), 4338 old_repos_relpath, old_rev, 4339 new_repos_relpath, new_rev, 4340 details->rev_author, details->added_rev); 4341 else 4342 return apr_psprintf(result_pool, 4343 _("Item switched from\n" 4344 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4345 "replaced with a file by %s in r%ld."), 4346 old_repos_relpath, old_rev, 4347 new_repos_relpath, new_rev, 4348 details->rev_author, details->added_rev); 4349 } 4350 else if (details->replacing_node_kind == svn_node_dir) 4351 { 4352 if (victim_node_kind == svn_node_dir) 4353 return apr_psprintf(result_pool, 4354 _("Directory switched from\n" 4355 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4356 "was a directory from another line of history " 4357 "before the replacement made by %s in r%ld."), 4358 old_repos_relpath, old_rev, 4359 new_repos_relpath, new_rev, 4360 details->rev_author, details->added_rev); 4361 else if (victim_node_kind == svn_node_file || 4362 victim_node_kind == svn_node_symlink) 4363 return apr_psprintf(result_pool, 4364 _("Directory switched from\n" 4365 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4366 "was a file before the replacement made by %s " 4367 "in r%ld."), 4368 old_repos_relpath, old_rev, 4369 new_repos_relpath, new_rev, 4370 details->rev_author, details->added_rev); 4371 else 4372 return apr_psprintf(result_pool, 4373 _("Item switched from\n" 4374 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4375 "replaced with a directory by %s in r%ld."), 4376 old_repos_relpath, old_rev, 4377 new_repos_relpath, new_rev, 4378 details->rev_author, details->added_rev); 4379 } 4380 else 4381 { 4382 if (victim_node_kind == svn_node_dir) 4383 return apr_psprintf(result_pool, 4384 _("Directory switched from\n" 4385 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4386 "did not exist before it was added by %s in " 4387 "r%ld."), 4388 old_repos_relpath, old_rev, 4389 new_repos_relpath, new_rev, 4390 details->rev_author, details->added_rev); 4391 else if (victim_node_kind == svn_node_file || 4392 victim_node_kind == svn_node_symlink) 4393 return apr_psprintf(result_pool, 4394 _("File switched from\n" 4395 "'^/%s@%ld'\nto\n'^/%s@%ld'\ndid " 4396 "not exist before it was added by %s in " 4397 "r%ld."), 4398 old_repos_relpath, old_rev, 4399 new_repos_relpath, new_rev, 4400 details->rev_author, details->added_rev); 4401 else 4402 return apr_psprintf(result_pool, 4403 _("Item switched from\n" 4404 "'^/%s@%ld'\nto\n'^/%s@%ld'\ndid " 4405 "not exist before it was added by %s in " 4406 "r%ld."), 4407 old_repos_relpath, old_rev, 4408 new_repos_relpath, new_rev, 4409 details->rev_author, details->added_rev); 4410 } 4411} 4412 4413static const char * 4414describe_incoming_deletion_upon_merge( 4415 struct conflict_tree_incoming_delete_details *details, 4416 svn_node_kind_t victim_node_kind, 4417 const char *old_repos_relpath, 4418 svn_revnum_t old_rev, 4419 const char *new_repos_relpath, 4420 svn_revnum_t new_rev, 4421 apr_pool_t *result_pool, 4422 apr_pool_t *scratch_pool) 4423{ 4424 if (details->replacing_node_kind == svn_node_file || 4425 details->replacing_node_kind == svn_node_symlink) 4426 { 4427 if (victim_node_kind == svn_node_dir) 4428 { 4429 const char *description = 4430 apr_psprintf(result_pool, 4431 _("Directory merged from\n" 4432 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4433 "was replaced with a file by %s in r%ld."), 4434 old_repos_relpath, old_rev, 4435 new_repos_relpath, new_rev, 4436 details->rev_author, details->deleted_rev); 4437 if (details->moves) 4438 { 4439 struct repos_move_info *move; 4440 4441 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4442 description = 4443 apr_psprintf(result_pool, 4444 _("%s\nThe replaced directory was moved to " 4445 "'^/%s'."), description, 4446 get_moved_to_repos_relpath(details, scratch_pool)); 4447 return append_moved_to_chain_description(description, 4448 move->next, 4449 result_pool, 4450 scratch_pool); 4451 } 4452 return description; 4453 } 4454 else if (victim_node_kind == svn_node_file || 4455 victim_node_kind == svn_node_symlink) 4456 { 4457 const char *description = 4458 apr_psprintf(result_pool, 4459 _("File merged from\n" 4460 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4461 "replaced with a file from another line of " 4462 "history by %s in r%ld."), 4463 old_repos_relpath, old_rev, 4464 new_repos_relpath, new_rev, 4465 details->rev_author, details->deleted_rev); 4466 if (details->moves) 4467 { 4468 struct repos_move_info *move; 4469 4470 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4471 description = 4472 apr_psprintf(result_pool, 4473 _("%s\nThe replaced file was moved to '^/%s'."), 4474 description, 4475 get_moved_to_repos_relpath(details, scratch_pool)); 4476 return append_moved_to_chain_description(description, 4477 move->next, 4478 result_pool, 4479 scratch_pool); 4480 } 4481 return description; 4482 } 4483 else 4484 return apr_psprintf(result_pool, 4485 _("Item merged from\n" 4486 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4487 "replaced with a file by %s in r%ld."), 4488 old_repos_relpath, old_rev, 4489 new_repos_relpath, new_rev, 4490 details->rev_author, details->deleted_rev); 4491 } 4492 else if (details->replacing_node_kind == svn_node_dir) 4493 { 4494 if (victim_node_kind == svn_node_dir) 4495 { 4496 const char *description = 4497 apr_psprintf(result_pool, 4498 _("Directory merged from\n" 4499 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4500 "was replaced with a directory from another " 4501 "line of history by %s in r%ld."), 4502 old_repos_relpath, old_rev, 4503 new_repos_relpath, new_rev, 4504 details->rev_author, details->deleted_rev); 4505 if (details->moves) 4506 { 4507 struct repos_move_info *move; 4508 4509 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4510 description = 4511 apr_psprintf(result_pool, 4512 _("%s\nThe replaced directory was moved to " 4513 "'^/%s'."), description, 4514 get_moved_to_repos_relpath(details, scratch_pool)); 4515 return append_moved_to_chain_description(description, 4516 move->next, 4517 result_pool, 4518 scratch_pool); 4519 } 4520 return description; 4521 } 4522 else if (victim_node_kind == svn_node_file || 4523 victim_node_kind == svn_node_symlink) 4524 { 4525 const char *description = 4526 apr_psprintf(result_pool, 4527 _("File merged from\n" 4528 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4529 "was replaced with a directory by %s in r%ld."), 4530 old_repos_relpath, old_rev, 4531 new_repos_relpath, new_rev, 4532 details->rev_author, details->deleted_rev); 4533 if (details->moves) 4534 { 4535 struct repos_move_info *move; 4536 4537 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4538 description = 4539 apr_psprintf(result_pool, 4540 _("%s\nThe replaced file was moved to '^/%s'."), 4541 description, 4542 get_moved_to_repos_relpath(details, scratch_pool)); 4543 return append_moved_to_chain_description(description, 4544 move->next, 4545 result_pool, 4546 scratch_pool); 4547 } 4548 return description; 4549 } 4550 else 4551 { 4552 const char *description = 4553 apr_psprintf(result_pool, 4554 _("Item merged from\n" 4555 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4556 "replaced with a directory by %s in r%ld."), 4557 old_repos_relpath, old_rev, 4558 new_repos_relpath, new_rev, 4559 details->rev_author, details->deleted_rev); 4560 if (details->moves) 4561 { 4562 struct repos_move_info *move; 4563 4564 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4565 description = 4566 apr_psprintf(result_pool, 4567 _("%s\nThe replaced item was moved to '^/%s'."), 4568 description, 4569 get_moved_to_repos_relpath(details, scratch_pool)); 4570 return append_moved_to_chain_description(description, 4571 move->next, 4572 result_pool, 4573 scratch_pool); 4574 } 4575 return description; 4576 } 4577 } 4578 else 4579 { 4580 if (victim_node_kind == svn_node_dir) 4581 { 4582 if (details->moves) 4583 { 4584 struct repos_move_info *move; 4585 const char *description; 4586 4587 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4588 description = 4589 apr_psprintf(result_pool, 4590 _("Directory merged from\n" 4591 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4592 "moved to '^/%s' by %s in r%ld."), 4593 old_repos_relpath, old_rev, 4594 new_repos_relpath, new_rev, 4595 get_moved_to_repos_relpath(details, scratch_pool), 4596 details->rev_author, details->deleted_rev); 4597 return append_moved_to_chain_description(description, 4598 move->next, 4599 result_pool, 4600 scratch_pool); 4601 } 4602 else 4603 return apr_psprintf(result_pool, 4604 _("Directory merged from\n" 4605 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4606 "deleted by %s in r%ld."), 4607 old_repos_relpath, old_rev, 4608 new_repos_relpath, new_rev, 4609 details->rev_author, details->deleted_rev); 4610 } 4611 else if (victim_node_kind == svn_node_file || 4612 victim_node_kind == svn_node_symlink) 4613 { 4614 if (details->moves) 4615 { 4616 struct repos_move_info *move; 4617 const char *description; 4618 4619 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4620 description = 4621 apr_psprintf(result_pool, 4622 _("File merged from\n" 4623 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4624 "moved to '^/%s' by %s in r%ld."), 4625 old_repos_relpath, old_rev, 4626 new_repos_relpath, new_rev, 4627 get_moved_to_repos_relpath(details, scratch_pool), 4628 details->rev_author, details->deleted_rev); 4629 return append_moved_to_chain_description(description, 4630 move->next, 4631 result_pool, 4632 scratch_pool); 4633 } 4634 else 4635 return apr_psprintf(result_pool, 4636 _("File merged from\n" 4637 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4638 "deleted by %s in r%ld."), 4639 old_repos_relpath, old_rev, 4640 new_repos_relpath, new_rev, 4641 details->rev_author, details->deleted_rev); 4642 } 4643 else 4644 { 4645 if (details->moves) 4646 { 4647 struct repos_move_info *move; 4648 const char *description; 4649 4650 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); 4651 description = 4652 apr_psprintf(result_pool, 4653 _("Item merged from\n" 4654 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4655 "moved to '^/%s' by %s in r%ld."), 4656 old_repos_relpath, old_rev, 4657 new_repos_relpath, new_rev, 4658 get_moved_to_repos_relpath(details, scratch_pool), 4659 details->rev_author, details->deleted_rev); 4660 return append_moved_to_chain_description(description, 4661 move->next, 4662 result_pool, 4663 scratch_pool); 4664 } 4665 else 4666 return apr_psprintf(result_pool, 4667 _("Item merged from\n" 4668 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas " 4669 "deleted by %s in r%ld."), 4670 old_repos_relpath, old_rev, 4671 new_repos_relpath, new_rev, 4672 details->rev_author, details->deleted_rev); 4673 } 4674 } 4675} 4676 4677static const char * 4678describe_incoming_reverse_addition_upon_merge( 4679 struct conflict_tree_incoming_delete_details *details, 4680 svn_node_kind_t victim_node_kind, 4681 const char *old_repos_relpath, 4682 svn_revnum_t old_rev, 4683 const char *new_repos_relpath, 4684 svn_revnum_t new_rev, 4685 apr_pool_t *result_pool) 4686{ 4687 if (details->replacing_node_kind == svn_node_file || 4688 details->replacing_node_kind == svn_node_symlink) 4689 { 4690 if (victim_node_kind == svn_node_dir) 4691 return apr_psprintf(result_pool, 4692 _("Directory reverse-merged from\n'^/%s@%ld'\nto " 4693 "^/%s@%ld was a file before the replacement " 4694 "made by %s in r%ld."), 4695 old_repos_relpath, old_rev, 4696 new_repos_relpath, new_rev, 4697 details->rev_author, details->added_rev); 4698 else if (victim_node_kind == svn_node_file || 4699 victim_node_kind == svn_node_symlink) 4700 return apr_psprintf(result_pool, 4701 _("File reverse-merged from\n" 4702 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4703 "was a file from another line of history before " 4704 "the replacement made by %s in r%ld."), 4705 old_repos_relpath, old_rev, 4706 new_repos_relpath, new_rev, 4707 details->rev_author, details->added_rev); 4708 else 4709 return apr_psprintf(result_pool, 4710 _("Item reverse-merged from\n" 4711 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4712 "was replaced with a file by %s in r%ld."), 4713 old_repos_relpath, old_rev, 4714 new_repos_relpath, new_rev, 4715 details->rev_author, details->added_rev); 4716 } 4717 else if (details->replacing_node_kind == svn_node_dir) 4718 { 4719 if (victim_node_kind == svn_node_dir) 4720 return apr_psprintf(result_pool, 4721 _("Directory reverse-merged from\n'^/%s@%ld'\nto " 4722 "^/%s@%ld was a directory from another line " 4723 "of history before the replacement made by %s " 4724 "in r%ld."), 4725 old_repos_relpath, old_rev, 4726 new_repos_relpath, new_rev, 4727 details->rev_author, details->added_rev); 4728 else if (victim_node_kind == svn_node_file || 4729 victim_node_kind == svn_node_symlink) 4730 return apr_psprintf(result_pool, 4731 _("Directory reverse-merged from\n'^/%s@%ld'\nto " 4732 "^/%s@%ld was a file before the replacement " 4733 "made by %s in r%ld."), 4734 old_repos_relpath, old_rev, 4735 new_repos_relpath, new_rev, 4736 details->rev_author, details->added_rev); 4737 else 4738 return apr_psprintf(result_pool, 4739 _("Item reverse-merged from\n" 4740 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4741 "was replaced with a directory by %s in r%ld."), 4742 old_repos_relpath, old_rev, 4743 new_repos_relpath, new_rev, 4744 details->rev_author, details->added_rev); 4745 } 4746 else 4747 { 4748 if (victim_node_kind == svn_node_dir) 4749 return apr_psprintf(result_pool, 4750 _("Directory reverse-merged from\n'^/%s@%ld'\nto " 4751 "^/%s@%ld did not exist before it was added " 4752 "by %s in r%ld."), 4753 old_repos_relpath, old_rev, 4754 new_repos_relpath, new_rev, 4755 details->rev_author, details->added_rev); 4756 else if (victim_node_kind == svn_node_file || 4757 victim_node_kind == svn_node_symlink) 4758 return apr_psprintf(result_pool, 4759 _("File reverse-merged from\n" 4760 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4761 "did not exist before it was added by %s in " 4762 "r%ld."), 4763 old_repos_relpath, old_rev, 4764 new_repos_relpath, new_rev, 4765 details->rev_author, details->added_rev); 4766 else 4767 return apr_psprintf(result_pool, 4768 _("Item reverse-merged from\n" 4769 "'^/%s@%ld'\nto\n'^/%s@%ld'\n" 4770 "did not exist before it was added by %s in " 4771 "r%ld."), 4772 old_repos_relpath, old_rev, 4773 new_repos_relpath, new_rev, 4774 details->rev_author, details->added_rev); 4775 } 4776} 4777 4778/* Implements tree_conflict_get_description_func_t. */ 4779static svn_error_t * 4780conflict_tree_get_description_incoming_delete( 4781 const char **incoming_change_description, 4782 svn_client_conflict_t *conflict, 4783 svn_client_ctx_t *ctx, 4784 apr_pool_t *result_pool, 4785 apr_pool_t *scratch_pool) 4786{ 4787 const char *action; 4788 svn_node_kind_t victim_node_kind; 4789 svn_wc_operation_t conflict_operation; 4790 const char *old_repos_relpath; 4791 svn_revnum_t old_rev; 4792 const char *new_repos_relpath; 4793 svn_revnum_t new_rev; 4794 struct conflict_tree_incoming_delete_details *details; 4795 4796 if (conflict->tree_conflict_incoming_details == NULL) 4797 return svn_error_trace(conflict_tree_get_incoming_description_generic( 4798 incoming_change_description, 4799 conflict, ctx, result_pool, scratch_pool)); 4800 4801 conflict_operation = svn_client_conflict_get_operation(conflict); 4802 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 4803 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 4804 &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool, 4805 scratch_pool)); 4806 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 4807 &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool, 4808 scratch_pool)); 4809 4810 details = conflict->tree_conflict_incoming_details; 4811 4812 if (conflict_operation == svn_wc_operation_update) 4813 { 4814 if (details->deleted_rev != SVN_INVALID_REVNUM) 4815 { 4816 action = describe_incoming_deletion_upon_update(details, 4817 victim_node_kind, 4818 old_rev, 4819 new_rev, 4820 result_pool, 4821 scratch_pool); 4822 } 4823 else /* details->added_rev != SVN_INVALID_REVNUM */ 4824 { 4825 /* This deletion is really the reverse change of an addition. */ 4826 action = describe_incoming_reverse_addition_upon_update( 4827 details, victim_node_kind, old_rev, new_rev, result_pool); 4828 } 4829 } 4830 else if (conflict_operation == svn_wc_operation_switch) 4831 { 4832 if (details->deleted_rev != SVN_INVALID_REVNUM) 4833 { 4834 action = describe_incoming_deletion_upon_switch(details, 4835 victim_node_kind, 4836 old_repos_relpath, 4837 old_rev, 4838 new_repos_relpath, 4839 new_rev, 4840 result_pool, 4841 scratch_pool); 4842 } 4843 else /* details->added_rev != SVN_INVALID_REVNUM */ 4844 { 4845 /* This deletion is really the reverse change of an addition. */ 4846 action = describe_incoming_reverse_addition_upon_switch( 4847 details, victim_node_kind, old_repos_relpath, old_rev, 4848 new_repos_relpath, new_rev, result_pool); 4849 4850 } 4851 } 4852 else if (conflict_operation == svn_wc_operation_merge) 4853 { 4854 if (details->deleted_rev != SVN_INVALID_REVNUM) 4855 { 4856 action = describe_incoming_deletion_upon_merge(details, 4857 victim_node_kind, 4858 old_repos_relpath, 4859 old_rev, 4860 new_repos_relpath, 4861 new_rev, 4862 result_pool, 4863 scratch_pool); 4864 } 4865 else /* details->added_rev != SVN_INVALID_REVNUM */ 4866 { 4867 /* This deletion is really the reverse change of an addition. */ 4868 action = describe_incoming_reverse_addition_upon_merge( 4869 details, victim_node_kind, old_repos_relpath, old_rev, 4870 new_repos_relpath, new_rev, result_pool); 4871 } 4872 } 4873 4874 *incoming_change_description = apr_pstrdup(result_pool, action); 4875 4876 return SVN_NO_ERROR; 4877} 4878 4879/* Baton for find_added_rev(). */ 4880struct find_added_rev_baton 4881{ 4882 const char *victim_abspath; 4883 svn_client_ctx_t *ctx; 4884 svn_revnum_t added_rev; 4885 const char *repos_relpath; 4886 const char *parent_repos_relpath; 4887 apr_pool_t *pool; 4888}; 4889 4890/* Implements svn_location_segment_receiver_t. 4891 * Finds the revision in which a node was added by tracing 'start' 4892 * revisions in location segments reported for the node. 4893 * If the PARENT_REPOS_RELPATH in the baton is not NULL, only consider 4894 * segments in which the node existed somwhere beneath this path. */ 4895static svn_error_t * 4896find_added_rev(svn_location_segment_t *segment, 4897 void *baton, 4898 apr_pool_t *scratch_pool) 4899{ 4900 struct find_added_rev_baton *b = baton; 4901 4902 if (b->ctx->notify_func2) 4903 { 4904 svn_wc_notify_t *notify; 4905 4906 notify = svn_wc_create_notify( 4907 b->victim_abspath, 4908 svn_wc_notify_tree_conflict_details_progress, 4909 scratch_pool), 4910 notify->revision = segment->range_start; 4911 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool); 4912 } 4913 4914 if (segment->path) /* not interested in gaps */ 4915 { 4916 if (b->parent_repos_relpath == NULL || 4917 svn_relpath_skip_ancestor(b->parent_repos_relpath, 4918 segment->path) != NULL) 4919 { 4920 b->added_rev = segment->range_start; 4921 b->repos_relpath = apr_pstrdup(b->pool, segment->path); 4922 } 4923 } 4924 4925 return SVN_NO_ERROR; 4926} 4927 4928/* Find conflict details in the case where a revision which added a node was 4929 * applied in reverse, resulting in an incoming deletion. */ 4930static svn_error_t * 4931get_incoming_delete_details_for_reverse_addition( 4932 struct conflict_tree_incoming_delete_details **details, 4933 const char *repos_root_url, 4934 const char *old_repos_relpath, 4935 svn_revnum_t old_rev, 4936 svn_revnum_t new_rev, 4937 svn_client_ctx_t *ctx, 4938 const char *victim_abspath, 4939 apr_pool_t *result_pool, 4940 apr_pool_t *scratch_pool) 4941{ 4942 svn_ra_session_t *ra_session; 4943 const char *url; 4944 const char *corrected_url; 4945 svn_string_t *author_revprop; 4946 struct find_added_rev_baton b = { 0 }; 4947 4948 url = svn_path_url_add_component2(repos_root_url, old_repos_relpath, 4949 scratch_pool); 4950 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, 4951 &corrected_url, 4952 url, NULL, NULL, 4953 FALSE, 4954 FALSE, 4955 ctx, 4956 scratch_pool, 4957 scratch_pool)); 4958 4959 *details = apr_pcalloc(result_pool, sizeof(**details)); 4960 b.ctx = ctx; 4961 b.victim_abspath = victim_abspath; 4962 b.added_rev = SVN_INVALID_REVNUM; 4963 b.repos_relpath = NULL; 4964 b.parent_repos_relpath = NULL; 4965 b.pool = scratch_pool; 4966 4967 /* Figure out when this node was added. */ 4968 SVN_ERR(svn_ra_get_location_segments(ra_session, "", old_rev, 4969 old_rev, new_rev, 4970 find_added_rev, &b, 4971 scratch_pool)); 4972 4973 SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev, 4974 SVN_PROP_REVISION_AUTHOR, 4975 &author_revprop, scratch_pool)); 4976 (*details)->deleted_rev = SVN_INVALID_REVNUM; 4977 (*details)->added_rev = b.added_rev; 4978 (*details)->repos_relpath = apr_pstrdup(result_pool, b.repos_relpath); 4979 if (author_revprop) 4980 (*details)->rev_author = apr_pstrdup(result_pool, author_revprop->data); 4981 else 4982 (*details)->rev_author = _("unknown author"); 4983 4984 /* Check for replacement. */ 4985 (*details)->replacing_node_kind = svn_node_none; 4986 if ((*details)->added_rev > 0) 4987 { 4988 svn_node_kind_t replaced_node_kind; 4989 4990 SVN_ERR(svn_ra_check_path(ra_session, "", 4991 rev_below((*details)->added_rev), 4992 &replaced_node_kind, scratch_pool)); 4993 if (replaced_node_kind != svn_node_none) 4994 SVN_ERR(svn_ra_check_path(ra_session, "", (*details)->added_rev, 4995 &(*details)->replacing_node_kind, 4996 scratch_pool)); 4997 } 4998 4999 return SVN_NO_ERROR; 5000} 5001 5002static svn_error_t * 5003init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, 5004 svn_client_conflict_t *conflict, 5005 svn_client_ctx_t *ctx, 5006 apr_pool_t *scratch_pool) 5007{ 5008 int i; 5009 const char *victim_abspath; 5010 svn_node_kind_t victim_node_kind; 5011 const char *incoming_new_repos_relpath; 5012 svn_revnum_t incoming_new_pegrev; 5013 5014 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 5015 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 5016 /* ### Should we get the old location in case of reverse-merges? */ 5017 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 5018 &incoming_new_repos_relpath, &incoming_new_pegrev, 5019 NULL, conflict, 5020 scratch_pool, scratch_pool)); 5021 details->wc_move_targets = apr_hash_make(conflict->pool); 5022 for (i = 0; i < details->moves->nelts; i++) 5023 { 5024 struct repos_move_info *move; 5025 5026 move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); 5027 SVN_ERR(follow_move_chains(details->wc_move_targets, move, 5028 ctx, victim_abspath, 5029 victim_node_kind, 5030 incoming_new_repos_relpath, 5031 incoming_new_pegrev, 5032 conflict->pool, scratch_pool)); 5033 } 5034 5035 /* Initialize to the first possible move target. Hopefully, 5036 * in most cases there will only be one candidate anyway. */ 5037 details->move_target_repos_relpath = 5038 get_moved_to_repos_relpath(details, scratch_pool); 5039 details->wc_move_target_idx = 0; 5040 5041 /* If only one move target exists recommend a resolution option. */ 5042 if (apr_hash_count(details->wc_move_targets) == 1) 5043 { 5044 apr_array_header_t *wc_abspaths; 5045 5046 wc_abspaths = svn_hash_gets(details->wc_move_targets, 5047 details->move_target_repos_relpath); 5048 if (wc_abspaths->nelts == 1) 5049 { 5050 svn_client_conflict_option_id_t recommended[] = 5051 { 5052 /* Only one of these will be present for any given conflict. */ 5053 svn_client_conflict_option_incoming_move_file_text_merge, 5054 svn_client_conflict_option_incoming_move_dir_merge, 5055 svn_client_conflict_option_local_move_file_text_merge, 5056 svn_client_conflict_option_local_move_dir_merge, 5057 svn_client_conflict_option_sibling_move_file_text_merge, 5058 svn_client_conflict_option_sibling_move_dir_merge, 5059 }; 5060 apr_array_header_t *options; 5061 5062 SVN_ERR(svn_client_conflict_tree_get_resolution_options( 5063 &options, conflict, ctx, scratch_pool, scratch_pool)); 5064 for (i = 0; i < (sizeof(recommended) / sizeof(recommended[0])); i++) 5065 { 5066 svn_client_conflict_option_id_t option_id = recommended[i]; 5067 5068 if (svn_client_conflict_option_find_by_id(options, option_id)) 5069 { 5070 conflict->recommended_option_id = option_id; 5071 break; 5072 } 5073 } 5074 } 5075 } 5076 5077 return SVN_NO_ERROR; 5078} 5079 5080/* Implements tree_conflict_get_details_func_t. 5081 * Find the revision in which the victim was deleted in the repository. */ 5082static svn_error_t * 5083conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict, 5084 svn_client_ctx_t *ctx, 5085 apr_pool_t *scratch_pool) 5086{ 5087 const char *old_repos_relpath; 5088 const char *new_repos_relpath; 5089 const char *repos_root_url; 5090 svn_revnum_t old_rev; 5091 svn_revnum_t new_rev; 5092 svn_node_kind_t old_kind; 5093 svn_node_kind_t new_kind; 5094 struct conflict_tree_incoming_delete_details *details; 5095 svn_wc_operation_t operation; 5096 5097 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 5098 &old_repos_relpath, &old_rev, &old_kind, conflict, scratch_pool, 5099 scratch_pool)); 5100 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 5101 &new_repos_relpath, &new_rev, &new_kind, conflict, scratch_pool, 5102 scratch_pool)); 5103 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 5104 conflict, 5105 scratch_pool, scratch_pool)); 5106 operation = svn_client_conflict_get_operation(conflict); 5107 5108 if (operation == svn_wc_operation_update) 5109 { 5110 if (old_rev < new_rev) 5111 { 5112 const char *parent_repos_relpath; 5113 svn_revnum_t parent_peg_rev; 5114 svn_revnum_t deleted_rev; 5115 svn_revnum_t end_rev; 5116 const char *deleted_rev_author; 5117 svn_node_kind_t replacing_node_kind; 5118 apr_array_header_t *moves; 5119 const char *related_repos_relpath; 5120 svn_revnum_t related_peg_rev; 5121 5122 /* The update operation went forward in history. */ 5123 SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev, 5124 &parent_repos_relpath, 5125 NULL, NULL, 5126 ctx->wc_ctx, 5127 svn_dirent_dirname( 5128 conflict->local_abspath, 5129 scratch_pool), 5130 scratch_pool, 5131 scratch_pool)); 5132 if (new_kind == svn_node_none) 5133 { 5134 SVN_ERR(find_related_node(&related_repos_relpath, 5135 &related_peg_rev, 5136 new_repos_relpath, new_rev, 5137 old_repos_relpath, old_rev, 5138 conflict, ctx, 5139 scratch_pool, scratch_pool)); 5140 } 5141 else 5142 { 5143 /* related to self */ 5144 related_repos_relpath = NULL; 5145 related_peg_rev = SVN_INVALID_REVNUM; 5146 } 5147 5148 end_rev = (new_kind == svn_node_none ? 0 : old_rev); 5149 if (end_rev >= parent_peg_rev) 5150 end_rev = (parent_peg_rev > 0 ? parent_peg_rev - 1 : 0); 5151 5152 SVN_ERR(find_revision_for_suspected_deletion( 5153 &deleted_rev, &deleted_rev_author, &replacing_node_kind, 5154 &moves, conflict, 5155 svn_dirent_basename(conflict->local_abspath, scratch_pool), 5156 parent_repos_relpath, parent_peg_rev, end_rev, 5157 related_repos_relpath, related_peg_rev, 5158 ctx, conflict->pool, scratch_pool)); 5159 if (deleted_rev == SVN_INVALID_REVNUM) 5160 { 5161 /* We could not determine the revision in which the node was 5162 * deleted. We cannot provide the required details so the best 5163 * we can do is fall back to the default description. */ 5164 return SVN_NO_ERROR; 5165 } 5166 5167 details = apr_pcalloc(conflict->pool, sizeof(*details)); 5168 details->deleted_rev = deleted_rev; 5169 details->added_rev = SVN_INVALID_REVNUM; 5170 details->repos_relpath = apr_pstrdup(conflict->pool, 5171 new_repos_relpath); 5172 details->rev_author = deleted_rev_author; 5173 details->replacing_node_kind = replacing_node_kind; 5174 details->moves = moves; 5175 } 5176 else /* new_rev < old_rev */ 5177 { 5178 /* The update operation went backwards in history. 5179 * Figure out when this node was added. */ 5180 SVN_ERR(get_incoming_delete_details_for_reverse_addition( 5181 &details, repos_root_url, old_repos_relpath, 5182 old_rev, new_rev, ctx, 5183 svn_client_conflict_get_local_abspath(conflict), 5184 conflict->pool, scratch_pool)); 5185 } 5186 } 5187 else if (operation == svn_wc_operation_switch || 5188 operation == svn_wc_operation_merge) 5189 { 5190 if (old_rev < new_rev) 5191 { 5192 svn_revnum_t deleted_rev; 5193 const char *deleted_rev_author; 5194 svn_node_kind_t replacing_node_kind; 5195 apr_array_header_t *moves; 5196 5197 /* The switch/merge operation went forward in history. 5198 * 5199 * The deletion of the node happened on the branch we switched to 5200 * or merged from. Scan new_repos_relpath's parent's log to find 5201 * the revision which deleted the node. */ 5202 SVN_ERR(find_revision_for_suspected_deletion( 5203 &deleted_rev, &deleted_rev_author, &replacing_node_kind, 5204 &moves, conflict, 5205 svn_relpath_basename(new_repos_relpath, scratch_pool), 5206 svn_relpath_dirname(new_repos_relpath, scratch_pool), 5207 new_rev, old_rev, old_repos_relpath, old_rev, ctx, 5208 conflict->pool, scratch_pool)); 5209 if (deleted_rev == SVN_INVALID_REVNUM) 5210 { 5211 /* We could not determine the revision in which the node was 5212 * deleted. We cannot provide the required details so the best 5213 * we can do is fall back to the default description. */ 5214 return SVN_NO_ERROR; 5215 } 5216 5217 details = apr_pcalloc(conflict->pool, sizeof(*details)); 5218 details->deleted_rev = deleted_rev; 5219 details->added_rev = SVN_INVALID_REVNUM; 5220 details->repos_relpath = apr_pstrdup(conflict->pool, 5221 new_repos_relpath); 5222 details->rev_author = apr_pstrdup(conflict->pool, 5223 deleted_rev_author); 5224 details->replacing_node_kind = replacing_node_kind; 5225 details->moves = moves; 5226 } 5227 else /* new_rev < old_rev */ 5228 { 5229 /* The switch/merge operation went backwards in history. 5230 * Figure out when the node we switched away from, or merged 5231 * from another branch, was added. */ 5232 SVN_ERR(get_incoming_delete_details_for_reverse_addition( 5233 &details, repos_root_url, old_repos_relpath, 5234 old_rev, new_rev, ctx, 5235 svn_client_conflict_get_local_abspath(conflict), 5236 conflict->pool, scratch_pool)); 5237 } 5238 } 5239 else 5240 { 5241 details = NULL; 5242 } 5243 5244 conflict->tree_conflict_incoming_details = details; 5245 5246 if (details && details->moves) 5247 SVN_ERR(init_wc_move_targets(details, conflict, ctx, scratch_pool)); 5248 5249 return SVN_NO_ERROR; 5250} 5251 5252/* Details for tree conflicts involving incoming additions. */ 5253struct conflict_tree_incoming_add_details 5254{ 5255 /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. */ 5256 svn_revnum_t added_rev; 5257 5258 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. 5259 * Note that both ADDED_REV and DELETED_REV may be valid for update/switch. 5260 * See comment in conflict_tree_get_details_incoming_add() for details. */ 5261 svn_revnum_t deleted_rev; 5262 5263 /* The path which was added/deleted relative to the repository root. */ 5264 const char *repos_relpath; 5265 5266 /* Authors who committed ADDED_REV/DELETED_REV. */ 5267 const char *added_rev_author; 5268 const char *deleted_rev_author; 5269 5270 /* Move information. If not NULL, this is an array of repos_move_info * 5271 * elements. Each element is the head of a move chain which starts in 5272 * ADDED_REV or in DELETED_REV (in which case moves should be interpreted 5273 * in reverse). */ 5274 apr_array_header_t *moves; 5275}; 5276 5277/* Implements tree_conflict_get_details_func_t. 5278 * Find the revision in which the victim was added in the repository. */ 5279static svn_error_t * 5280conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict, 5281 svn_client_ctx_t *ctx, 5282 apr_pool_t *scratch_pool) 5283{ 5284 const char *old_repos_relpath; 5285 const char *new_repos_relpath; 5286 const char *repos_root_url; 5287 svn_revnum_t old_rev; 5288 svn_revnum_t new_rev; 5289 struct conflict_tree_incoming_add_details *details = NULL; 5290 svn_wc_operation_t operation; 5291 5292 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 5293 &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool, 5294 scratch_pool)); 5295 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 5296 &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool, 5297 scratch_pool)); 5298 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 5299 conflict, 5300 scratch_pool, scratch_pool)); 5301 operation = svn_client_conflict_get_operation(conflict); 5302 5303 if (operation == svn_wc_operation_update || 5304 operation == svn_wc_operation_switch) 5305 { 5306 /* Only the new repository location is recorded for the node which 5307 * caused an incoming addition. There is no pre-update/pre-switch 5308 * revision to be recorded for the node since it does not exist in 5309 * the repository at that revision. 5310 * The implication is that we cannot know whether the operation went 5311 * forward or backwards in history. So always try to find an added 5312 * and a deleted revision for the node. Users must figure out by whether 5313 * the addition or deletion caused the conflict. */ 5314 const char *url; 5315 const char *corrected_url; 5316 svn_string_t *author_revprop; 5317 struct find_added_rev_baton b = { 0 }; 5318 svn_ra_session_t *ra_session; 5319 svn_revnum_t deleted_rev; 5320 svn_revnum_t head_rev; 5321 5322 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath, 5323 scratch_pool); 5324 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, 5325 &corrected_url, 5326 url, NULL, NULL, 5327 FALSE, 5328 FALSE, 5329 ctx, 5330 scratch_pool, 5331 scratch_pool)); 5332 5333 details = apr_pcalloc(conflict->pool, sizeof(*details)); 5334 b.ctx = ctx, 5335 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict), 5336 b.added_rev = SVN_INVALID_REVNUM; 5337 b.repos_relpath = NULL; 5338 b.parent_repos_relpath = NULL; 5339 b.pool = scratch_pool; 5340 5341 /* Figure out when this node was added. */ 5342 SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev, 5343 new_rev, SVN_INVALID_REVNUM, 5344 find_added_rev, &b, 5345 scratch_pool)); 5346 5347 SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev, 5348 SVN_PROP_REVISION_AUTHOR, 5349 &author_revprop, scratch_pool)); 5350 details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath); 5351 details->added_rev = b.added_rev; 5352 if (author_revprop) 5353 details->added_rev_author = apr_pstrdup(conflict->pool, 5354 author_revprop->data); 5355 else 5356 details->added_rev_author = _("unknown author"); 5357 details->deleted_rev = SVN_INVALID_REVNUM; 5358 details->deleted_rev_author = NULL; 5359 5360 /* Figure out whether this node was deleted later. 5361 * ### Could probably optimize by infering both addition and deletion 5362 * ### from svn_ra_get_location_segments() call above. */ 5363 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &head_rev, scratch_pool)); 5364 if (new_rev < head_rev) 5365 { 5366 SVN_ERR(svn_ra_get_deleted_rev(ra_session, "", new_rev, head_rev, 5367 &deleted_rev, scratch_pool)); 5368 if (SVN_IS_VALID_REVNUM(deleted_rev)) 5369 { 5370 SVN_ERR(svn_ra_rev_prop(ra_session, deleted_rev, 5371 SVN_PROP_REVISION_AUTHOR, 5372 &author_revprop, scratch_pool)); 5373 details->deleted_rev = deleted_rev; 5374 if (author_revprop) 5375 details->deleted_rev_author = apr_pstrdup(conflict->pool, 5376 author_revprop->data); 5377 else 5378 details->deleted_rev_author = _("unknown author"); 5379 } 5380 } 5381 } 5382 else if (operation == svn_wc_operation_merge && 5383 strcmp(old_repos_relpath, new_repos_relpath) == 0) 5384 { 5385 if (old_rev < new_rev) 5386 { 5387 /* The merge operation went forwards in history. 5388 * The addition of the node happened on the branch we merged form. 5389 * Scan the nodes's history to find the revision which added it. */ 5390 const char *url; 5391 const char *corrected_url; 5392 svn_string_t *author_revprop; 5393 struct find_added_rev_baton b = { 0 }; 5394 svn_ra_session_t *ra_session; 5395 5396 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath, 5397 scratch_pool); 5398 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, 5399 &corrected_url, 5400 url, NULL, NULL, 5401 FALSE, 5402 FALSE, 5403 ctx, 5404 scratch_pool, 5405 scratch_pool)); 5406 5407 details = apr_pcalloc(conflict->pool, sizeof(*details)); 5408 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict); 5409 b.ctx = ctx; 5410 b.added_rev = SVN_INVALID_REVNUM; 5411 b.repos_relpath = NULL; 5412 b.parent_repos_relpath = NULL; 5413 b.pool = scratch_pool; 5414 5415 /* Figure out when this node was added. */ 5416 SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev, 5417 new_rev, old_rev, 5418 find_added_rev, &b, 5419 scratch_pool)); 5420 5421 SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev, 5422 SVN_PROP_REVISION_AUTHOR, 5423 &author_revprop, scratch_pool)); 5424 details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath); 5425 details->added_rev = b.added_rev; 5426 if (author_revprop) 5427 details->added_rev_author = apr_pstrdup(conflict->pool, 5428 author_revprop->data); 5429 else 5430 details->added_rev_author = _("unknown author"); 5431 details->deleted_rev = SVN_INVALID_REVNUM; 5432 details->deleted_rev_author = NULL; 5433 } 5434 else if (old_rev > new_rev) 5435 { 5436 /* The merge operation was a reverse-merge. 5437 * This addition is in fact a deletion, applied in reverse, 5438 * which happened on the branch we merged from. 5439 * Find the revision which deleted the node. */ 5440 svn_revnum_t deleted_rev; 5441 const char *deleted_rev_author; 5442 svn_node_kind_t replacing_node_kind; 5443 apr_array_header_t *moves; 5444 5445 SVN_ERR(find_revision_for_suspected_deletion( 5446 &deleted_rev, &deleted_rev_author, &replacing_node_kind, 5447 &moves, conflict, 5448 svn_relpath_basename(old_repos_relpath, scratch_pool), 5449 svn_relpath_dirname(old_repos_relpath, scratch_pool), 5450 old_rev, new_rev, 5451 NULL, SVN_INVALID_REVNUM, /* related to self */ 5452 ctx, 5453 conflict->pool, scratch_pool)); 5454 if (deleted_rev == SVN_INVALID_REVNUM) 5455 { 5456 /* We could not determine the revision in which the node was 5457 * deleted. We cannot provide the required details so the best 5458 * we can do is fall back to the default description. */ 5459 return SVN_NO_ERROR; 5460 } 5461 5462 details = apr_pcalloc(conflict->pool, sizeof(*details)); 5463 details->repos_relpath = apr_pstrdup(conflict->pool, 5464 new_repos_relpath); 5465 details->deleted_rev = deleted_rev; 5466 details->deleted_rev_author = apr_pstrdup(conflict->pool, 5467 deleted_rev_author); 5468 5469 details->added_rev = SVN_INVALID_REVNUM; 5470 details->added_rev_author = NULL; 5471 details->moves = moves; 5472 } 5473 } 5474 5475 conflict->tree_conflict_incoming_details = details; 5476 5477 return SVN_NO_ERROR; 5478} 5479 5480static const char * 5481describe_incoming_add_upon_update( 5482 struct conflict_tree_incoming_add_details *details, 5483 svn_node_kind_t new_node_kind, 5484 svn_revnum_t new_rev, 5485 apr_pool_t *result_pool) 5486{ 5487 if (new_node_kind == svn_node_dir) 5488 { 5489 if (SVN_IS_VALID_REVNUM(details->added_rev) && 5490 SVN_IS_VALID_REVNUM(details->deleted_rev)) 5491 return apr_psprintf(result_pool, 5492 _("A new directory appeared during update to r%ld; " 5493 "it was added by %s in r%ld and later deleted " 5494 "by %s in r%ld."), new_rev, 5495 details->added_rev_author, details->added_rev, 5496 details->deleted_rev_author, details->deleted_rev); 5497 else if (SVN_IS_VALID_REVNUM(details->added_rev)) 5498 return apr_psprintf(result_pool, 5499 _("A new directory appeared during update to r%ld; " 5500 "it was added by %s in r%ld."), new_rev, 5501 details->added_rev_author, details->added_rev); 5502 else 5503 return apr_psprintf(result_pool, 5504 _("A new directory appeared during update to r%ld; " 5505 "it was deleted by %s in r%ld."), new_rev, 5506 details->deleted_rev_author, details->deleted_rev); 5507 } 5508 else if (new_node_kind == svn_node_file || 5509 new_node_kind == svn_node_symlink) 5510 { 5511 if (SVN_IS_VALID_REVNUM(details->added_rev) && 5512 SVN_IS_VALID_REVNUM(details->deleted_rev)) 5513 return apr_psprintf(result_pool, 5514 _("A new file appeared during update to r%ld; " 5515 "it was added by %s in r%ld and later deleted " 5516 "by %s in r%ld."), new_rev, 5517 details->added_rev_author, details->added_rev, 5518 details->deleted_rev_author, details->deleted_rev); 5519 else if (SVN_IS_VALID_REVNUM(details->added_rev)) 5520 return apr_psprintf(result_pool, 5521 _("A new file appeared during update to r%ld; " 5522 "it was added by %s in r%ld."), new_rev, 5523 details->added_rev_author, details->added_rev); 5524 else 5525 return apr_psprintf(result_pool, 5526 _("A new file appeared during update to r%ld; " 5527 "it was deleted by %s in r%ld."), new_rev, 5528 details->deleted_rev_author, details->deleted_rev); 5529 } 5530 else 5531 { 5532 if (SVN_IS_VALID_REVNUM(details->added_rev) && 5533 SVN_IS_VALID_REVNUM(details->deleted_rev)) 5534 return apr_psprintf(result_pool, 5535 _("A new item appeared during update to r%ld; " 5536 "it was added by %s in r%ld and later deleted " 5537 "by %s in r%ld."), new_rev, 5538 details->added_rev_author, details->added_rev, 5539 details->deleted_rev_author, details->deleted_rev); 5540 else if (SVN_IS_VALID_REVNUM(details->added_rev)) 5541 return apr_psprintf(result_pool, 5542 _("A new item appeared during update to r%ld; " 5543 "it was added by %s in r%ld."), new_rev, 5544 details->added_rev_author, details->added_rev); 5545 else 5546 return apr_psprintf(result_pool, 5547 _("A new item appeared during update to r%ld; " 5548 "it was deleted by %s in r%ld."), new_rev, 5549 details->deleted_rev_author, details->deleted_rev); 5550 } 5551} 5552 5553static const char * 5554describe_incoming_add_upon_switch( 5555 struct conflict_tree_incoming_add_details *details, 5556 svn_node_kind_t victim_node_kind, 5557 const char *new_repos_relpath, 5558 svn_revnum_t new_rev, 5559 apr_pool_t *result_pool) 5560{ 5561 if (victim_node_kind == svn_node_dir) 5562 { 5563 if (SVN_IS_VALID_REVNUM(details->added_rev) && 5564 SVN_IS_VALID_REVNUM(details->deleted_rev)) 5565 return apr_psprintf(result_pool, 5566 _("A new directory appeared during switch to\n" 5567 "'^/%s@%ld'.\n" 5568 "It was added by %s in r%ld and later deleted " 5569 "by %s in r%ld."), new_repos_relpath, new_rev, 5570 details->added_rev_author, details->added_rev, 5571 details->deleted_rev_author, details->deleted_rev); 5572 else if (SVN_IS_VALID_REVNUM(details->added_rev)) 5573 return apr_psprintf(result_pool, 5574 _("A new directory appeared during switch to\n" 5575 "'^/%s@%ld'.\nIt was added by %s in r%ld."), 5576 new_repos_relpath, new_rev, 5577 details->added_rev_author, details->added_rev); 5578 else 5579 return apr_psprintf(result_pool, 5580 _("A new directory appeared during switch to\n" 5581 "'^/%s@%ld'.\nIt was deleted by %s in r%ld."), 5582 new_repos_relpath, new_rev, 5583 details->deleted_rev_author, details->deleted_rev); 5584 } 5585 else if (victim_node_kind == svn_node_file || 5586 victim_node_kind == svn_node_symlink) 5587 { 5588 if (SVN_IS_VALID_REVNUM(details->added_rev) && 5589 SVN_IS_VALID_REVNUM(details->deleted_rev)) 5590 return apr_psprintf(result_pool, 5591 _("A new file appeared during switch to\n" 5592 "'^/%s@%ld'.\n" 5593 "It was added by %s in r%ld and later deleted " 5594 "by %s in r%ld."), new_repos_relpath, new_rev, 5595 details->added_rev_author, details->added_rev, 5596 details->deleted_rev_author, details->deleted_rev); 5597 else if (SVN_IS_VALID_REVNUM(details->added_rev)) 5598 return apr_psprintf(result_pool, 5599 _("A new file appeared during switch to\n" 5600 "'^/%s@%ld'.\n" 5601 "It was added by %s in r%ld."), 5602 new_repos_relpath, new_rev, 5603 details->added_rev_author, details->added_rev); 5604 else 5605 return apr_psprintf(result_pool, 5606 _("A new file appeared during switch to\n" 5607 "'^/%s@%ld'.\n" 5608 "It was deleted by %s in r%ld."), 5609 new_repos_relpath, new_rev, 5610 details->deleted_rev_author, details->deleted_rev); 5611 } 5612 else 5613 { 5614 if (SVN_IS_VALID_REVNUM(details->added_rev) && 5615 SVN_IS_VALID_REVNUM(details->deleted_rev)) 5616 return apr_psprintf(result_pool, 5617 _("A new item appeared during switch to\n" 5618 "'^/%s@%ld'.\n" 5619 "It was added by %s in r%ld and later deleted " 5620 "by %s in r%ld."), new_repos_relpath, new_rev, 5621 details->added_rev_author, details->added_rev, 5622 details->deleted_rev_author, details->deleted_rev); 5623 else if (SVN_IS_VALID_REVNUM(details->added_rev)) 5624 return apr_psprintf(result_pool, 5625 _("A new item appeared during switch to\n" 5626 "'^/%s@%ld'.\n" 5627 "It was added by %s in r%ld."), 5628 new_repos_relpath, new_rev, 5629 details->added_rev_author, details->added_rev); 5630 else 5631 return apr_psprintf(result_pool, 5632 _("A new item appeared during switch to\n" 5633 "'^/%s@%ld'.\n" 5634 "It was deleted by %s in r%ld."), 5635 new_repos_relpath, new_rev, 5636 details->deleted_rev_author, details->deleted_rev); 5637 } 5638} 5639 5640static const char * 5641describe_incoming_add_upon_merge( 5642 struct conflict_tree_incoming_add_details *details, 5643 svn_node_kind_t new_node_kind, 5644 svn_revnum_t old_rev, 5645 const char *new_repos_relpath, 5646 svn_revnum_t new_rev, 5647 apr_pool_t *result_pool) 5648{ 5649 if (new_node_kind == svn_node_dir) 5650 { 5651 if (old_rev + 1 == new_rev) 5652 return apr_psprintf(result_pool, 5653 _("A new directory appeared during merge of\n" 5654 "'^/%s:%ld'.\nIt was added by %s in r%ld."), 5655 new_repos_relpath, new_rev, 5656 details->added_rev_author, details->added_rev); 5657 else 5658 return apr_psprintf(result_pool, 5659 _("A new directory appeared during merge of\n" 5660 "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."), 5661 new_repos_relpath, old_rev + 1, new_rev, 5662 details->added_rev_author, details->added_rev); 5663 } 5664 else if (new_node_kind == svn_node_file || 5665 new_node_kind == svn_node_symlink) 5666 { 5667 if (old_rev + 1 == new_rev) 5668 return apr_psprintf(result_pool, 5669 _("A new file appeared during merge of\n" 5670 "'^/%s:%ld'.\nIt was added by %s in r%ld."), 5671 new_repos_relpath, new_rev, 5672 details->added_rev_author, details->added_rev); 5673 else 5674 return apr_psprintf(result_pool, 5675 _("A new file appeared during merge of\n" 5676 "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."), 5677 new_repos_relpath, old_rev + 1, new_rev, 5678 details->added_rev_author, details->added_rev); 5679 } 5680 else 5681 { 5682 if (old_rev + 1 == new_rev) 5683 return apr_psprintf(result_pool, 5684 _("A new item appeared during merge of\n" 5685 "'^/%s:%ld'.\nIt was added by %s in r%ld."), 5686 new_repos_relpath, new_rev, 5687 details->added_rev_author, details->added_rev); 5688 else 5689 return apr_psprintf(result_pool, 5690 _("A new item appeared during merge of\n" 5691 "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."), 5692 new_repos_relpath, old_rev + 1, new_rev, 5693 details->added_rev_author, details->added_rev); 5694 } 5695} 5696 5697static const char * 5698describe_incoming_reverse_deletion_upon_merge( 5699 struct conflict_tree_incoming_add_details *details, 5700 svn_node_kind_t new_node_kind, 5701 const char *old_repos_relpath, 5702 svn_revnum_t old_rev, 5703 svn_revnum_t new_rev, 5704 apr_pool_t *result_pool) 5705{ 5706 if (new_node_kind == svn_node_dir) 5707 { 5708 if (new_rev + 1 == old_rev) 5709 return apr_psprintf(result_pool, 5710 _("A new directory appeared during reverse-merge of" 5711 "\n'^/%s:%ld'.\nIt was deleted by %s in r%ld."), 5712 old_repos_relpath, old_rev, 5713 details->deleted_rev_author, 5714 details->deleted_rev); 5715 else 5716 return apr_psprintf(result_pool, 5717 _("A new directory appeared during reverse-merge " 5718 "of\n'^/%s:%ld-%ld'.\n" 5719 "It was deleted by %s in r%ld."), 5720 old_repos_relpath, new_rev, rev_below(old_rev), 5721 details->deleted_rev_author, 5722 details->deleted_rev); 5723 } 5724 else if (new_node_kind == svn_node_file || 5725 new_node_kind == svn_node_symlink) 5726 { 5727 if (new_rev + 1 == old_rev) 5728 return apr_psprintf(result_pool, 5729 _("A new file appeared during reverse-merge of\n" 5730 "'^/%s:%ld'.\nIt was deleted by %s in r%ld."), 5731 old_repos_relpath, old_rev, 5732 details->deleted_rev_author, 5733 details->deleted_rev); 5734 else 5735 return apr_psprintf(result_pool, 5736 _("A new file appeared during reverse-merge of\n" 5737 "'^/%s:%ld-%ld'.\nIt was deleted by %s in r%ld."), 5738 old_repos_relpath, new_rev + 1, old_rev, 5739 details->deleted_rev_author, 5740 details->deleted_rev); 5741 } 5742 else 5743 { 5744 if (new_rev + 1 == old_rev) 5745 return apr_psprintf(result_pool, 5746 _("A new item appeared during reverse-merge of\n" 5747 "'^/%s:%ld'.\nIt was deleted by %s in r%ld."), 5748 old_repos_relpath, old_rev, 5749 details->deleted_rev_author, 5750 details->deleted_rev); 5751 else 5752 return apr_psprintf(result_pool, 5753 _("A new item appeared during reverse-merge of\n" 5754 "'^/%s:%ld-%ld'.\nIt was deleted by %s in r%ld."), 5755 old_repos_relpath, new_rev + 1, old_rev, 5756 details->deleted_rev_author, 5757 details->deleted_rev); 5758 } 5759} 5760 5761/* Implements tree_conflict_get_description_func_t. */ 5762static svn_error_t * 5763conflict_tree_get_description_incoming_add( 5764 const char **incoming_change_description, 5765 svn_client_conflict_t *conflict, 5766 svn_client_ctx_t *ctx, 5767 apr_pool_t *result_pool, 5768 apr_pool_t *scratch_pool) 5769{ 5770 const char *action; 5771 svn_node_kind_t victim_node_kind; 5772 svn_wc_operation_t conflict_operation; 5773 const char *old_repos_relpath; 5774 svn_revnum_t old_rev; 5775 svn_node_kind_t old_node_kind; 5776 const char *new_repos_relpath; 5777 svn_revnum_t new_rev; 5778 svn_node_kind_t new_node_kind; 5779 struct conflict_tree_incoming_add_details *details; 5780 5781 if (conflict->tree_conflict_incoming_details == NULL) 5782 return svn_error_trace(conflict_tree_get_incoming_description_generic( 5783 incoming_change_description, conflict, ctx, 5784 result_pool, scratch_pool)); 5785 5786 conflict_operation = svn_client_conflict_get_operation(conflict); 5787 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 5788 5789 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 5790 &old_repos_relpath, &old_rev, &old_node_kind, conflict, 5791 scratch_pool, scratch_pool)); 5792 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 5793 &new_repos_relpath, &new_rev, &new_node_kind, conflict, 5794 scratch_pool, scratch_pool)); 5795 5796 details = conflict->tree_conflict_incoming_details; 5797 5798 if (conflict_operation == svn_wc_operation_update) 5799 { 5800 action = describe_incoming_add_upon_update(details, 5801 new_node_kind, 5802 new_rev, 5803 result_pool); 5804 } 5805 else if (conflict_operation == svn_wc_operation_switch) 5806 { 5807 action = describe_incoming_add_upon_switch(details, 5808 victim_node_kind, 5809 new_repos_relpath, 5810 new_rev, 5811 result_pool); 5812 } 5813 else if (conflict_operation == svn_wc_operation_merge) 5814 { 5815 if (old_rev < new_rev) 5816 action = describe_incoming_add_upon_merge(details, 5817 new_node_kind, 5818 old_rev, 5819 new_repos_relpath, 5820 new_rev, 5821 result_pool); 5822 else 5823 action = describe_incoming_reverse_deletion_upon_merge( 5824 details, new_node_kind, old_repos_relpath, 5825 old_rev, new_rev, result_pool); 5826 } 5827 5828 *incoming_change_description = apr_pstrdup(result_pool, action); 5829 5830 return SVN_NO_ERROR; 5831} 5832 5833/* Details for tree conflicts involving incoming edits. 5834 * Note that we store an array of these. Each element corresponds to a 5835 * revision within the old/new range in which a modification occured. */ 5836struct conflict_tree_incoming_edit_details 5837{ 5838 /* The revision in which the edit ocurred. */ 5839 svn_revnum_t rev; 5840 5841 /* The author of the revision. */ 5842 const char *author; 5843 5844 /** Is the text modified? May be svn_tristate_unknown. */ 5845 svn_tristate_t text_modified; 5846 5847 /** Are properties modified? May be svn_tristate_unknown. */ 5848 svn_tristate_t props_modified; 5849 5850 /** For directories, are children modified? 5851 * May be svn_tristate_unknown. */ 5852 svn_tristate_t children_modified; 5853 5854 /* The path which was edited, relative to the repository root. */ 5855 const char *repos_relpath; 5856}; 5857 5858/* Baton for find_modified_rev(). */ 5859struct find_modified_rev_baton { 5860 const char *victim_abspath; 5861 svn_client_ctx_t *ctx; 5862 apr_array_header_t *edits; 5863 const char *repos_relpath; 5864 svn_node_kind_t node_kind; 5865 apr_pool_t *result_pool; 5866 apr_pool_t *scratch_pool; 5867}; 5868 5869/* Implements svn_log_entry_receiver_t. */ 5870static svn_error_t * 5871find_modified_rev(void *baton, 5872 svn_log_entry_t *log_entry, 5873 apr_pool_t *scratch_pool) 5874{ 5875 struct find_modified_rev_baton *b = baton; 5876 struct conflict_tree_incoming_edit_details *details = NULL; 5877 svn_string_t *author; 5878 apr_hash_index_t *hi; 5879 apr_pool_t *iterpool; 5880 5881 if (b->ctx->notify_func2) 5882 { 5883 svn_wc_notify_t *notify; 5884 5885 notify = svn_wc_create_notify( 5886 b->victim_abspath, 5887 svn_wc_notify_tree_conflict_details_progress, 5888 scratch_pool), 5889 notify->revision = log_entry->revision; 5890 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool); 5891 } 5892 5893 /* No paths were changed in this revision. Nothing to do. */ 5894 if (! log_entry->changed_paths2) 5895 return SVN_NO_ERROR; 5896 5897 details = apr_pcalloc(b->result_pool, sizeof(*details)); 5898 details->rev = log_entry->revision; 5899 author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR); 5900 if (author) 5901 details->author = apr_pstrdup(b->result_pool, author->data); 5902 else 5903 details->author = _("unknown author"); 5904 5905 details->text_modified = svn_tristate_unknown; 5906 details->props_modified = svn_tristate_unknown; 5907 details->children_modified = svn_tristate_unknown; 5908 5909 iterpool = svn_pool_create(scratch_pool); 5910 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2); 5911 hi != NULL; 5912 hi = apr_hash_next(hi)) 5913 { 5914 void *val; 5915 const char *path; 5916 svn_log_changed_path2_t *log_item; 5917 5918 svn_pool_clear(iterpool); 5919 5920 apr_hash_this(hi, (void *) &path, NULL, &val); 5921 log_item = val; 5922 5923 /* ### Remove leading slash from paths in log entries. */ 5924 if (path[0] == '/') 5925 path = svn_relpath_canonicalize(path, iterpool); 5926 5927 if (svn_path_compare_paths(b->repos_relpath, path) == 0 && 5928 (log_item->action == 'M' || log_item->action == 'A')) 5929 { 5930 details->text_modified = log_item->text_modified; 5931 details->props_modified = log_item->props_modified; 5932 details->repos_relpath = apr_pstrdup(b->result_pool, path); 5933 5934 if (log_item->copyfrom_path) 5935 b->repos_relpath = apr_pstrdup(b->scratch_pool, 5936 /* ### remove leading slash */ 5937 svn_relpath_canonicalize( 5938 log_item->copyfrom_path, 5939 iterpool)); 5940 } 5941 else if (b->node_kind == svn_node_dir && 5942 svn_relpath_skip_ancestor(b->repos_relpath, path) != NULL) 5943 details->children_modified = svn_tristate_true; 5944 } 5945 5946 if (b->node_kind == svn_node_dir && 5947 details->children_modified == svn_tristate_unknown) 5948 details->children_modified = svn_tristate_false; 5949 5950 APR_ARRAY_PUSH(b->edits, struct conflict_tree_incoming_edit_details *) = 5951 details; 5952 5953 svn_pool_destroy(iterpool); 5954 5955 return SVN_NO_ERROR; 5956} 5957 5958/* Implements tree_conflict_get_details_func_t. 5959 * Find one or more revisions in which the victim was modified in the 5960 * repository. */ 5961static svn_error_t * 5962conflict_tree_get_details_incoming_edit(svn_client_conflict_t *conflict, 5963 svn_client_ctx_t *ctx, 5964 apr_pool_t *scratch_pool) 5965{ 5966 const char *old_repos_relpath; 5967 const char *new_repos_relpath; 5968 const char *repos_root_url; 5969 svn_revnum_t old_rev; 5970 svn_revnum_t new_rev; 5971 svn_node_kind_t old_node_kind; 5972 svn_node_kind_t new_node_kind; 5973 svn_wc_operation_t operation; 5974 const char *url; 5975 const char *corrected_url; 5976 svn_ra_session_t *ra_session; 5977 apr_array_header_t *paths; 5978 apr_array_header_t *revprops; 5979 struct find_modified_rev_baton b = { 0 }; 5980 5981 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 5982 &old_repos_relpath, &old_rev, &old_node_kind, conflict, 5983 scratch_pool, scratch_pool)); 5984 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 5985 &new_repos_relpath, &new_rev, &new_node_kind, conflict, 5986 scratch_pool, scratch_pool)); 5987 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 5988 conflict, 5989 scratch_pool, scratch_pool)); 5990 operation = svn_client_conflict_get_operation(conflict); 5991 if (operation == svn_wc_operation_update) 5992 { 5993 b.node_kind = old_rev < new_rev ? new_node_kind : old_node_kind; 5994 5995 /* If there is no node then we cannot find any edits. */ 5996 if (b.node_kind == svn_node_none) 5997 return SVN_NO_ERROR; 5998 5999 url = svn_path_url_add_component2(repos_root_url, 6000 old_rev < new_rev ? new_repos_relpath 6001 : old_repos_relpath, 6002 scratch_pool); 6003 6004 b.repos_relpath = old_rev < new_rev ? new_repos_relpath 6005 : old_repos_relpath; 6006 } 6007 else if (operation == svn_wc_operation_switch || 6008 operation == svn_wc_operation_merge) 6009 { 6010 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath, 6011 scratch_pool); 6012 6013 b.repos_relpath = new_repos_relpath; 6014 b.node_kind = new_node_kind; 6015 } 6016 6017 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, 6018 &corrected_url, 6019 url, NULL, NULL, 6020 FALSE, 6021 FALSE, 6022 ctx, 6023 scratch_pool, 6024 scratch_pool)); 6025 6026 paths = apr_array_make(scratch_pool, 1, sizeof(const char *)); 6027 APR_ARRAY_PUSH(paths, const char *) = ""; 6028 6029 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *)); 6030 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; 6031 6032 b.ctx = ctx; 6033 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict); 6034 b.result_pool = conflict->pool; 6035 b.scratch_pool = scratch_pool; 6036 b.edits = apr_array_make( 6037 conflict->pool, 0, 6038 sizeof(struct conflict_tree_incoming_edit_details *)); 6039 6040 SVN_ERR(svn_ra_get_log2(ra_session, paths, 6041 old_rev < new_rev ? old_rev : new_rev, 6042 old_rev < new_rev ? new_rev : old_rev, 6043 0, /* no limit */ 6044 TRUE, /* need the changed paths list */ 6045 FALSE, /* need to traverse copies */ 6046 FALSE, /* no need for merged revisions */ 6047 revprops, 6048 find_modified_rev, &b, 6049 scratch_pool)); 6050 6051 conflict->tree_conflict_incoming_details = b.edits; 6052 6053 return SVN_NO_ERROR; 6054} 6055 6056static const char * 6057describe_incoming_edit_upon_update(svn_revnum_t old_rev, 6058 svn_revnum_t new_rev, 6059 svn_node_kind_t old_node_kind, 6060 svn_node_kind_t new_node_kind, 6061 apr_pool_t *result_pool) 6062{ 6063 if (old_rev < new_rev) 6064 { 6065 if (new_node_kind == svn_node_dir) 6066 return apr_psprintf(result_pool, 6067 _("Changes destined for a directory arrived " 6068 "via the following revisions during update " 6069 "from r%ld to r%ld."), old_rev, new_rev); 6070 else if (new_node_kind == svn_node_file || 6071 new_node_kind == svn_node_symlink) 6072 return apr_psprintf(result_pool, 6073 _("Changes destined for a file arrived " 6074 "via the following revisions during update " 6075 "from r%ld to r%ld"), old_rev, new_rev); 6076 else 6077 return apr_psprintf(result_pool, 6078 _("Changes from the following revisions arrived " 6079 "during update from r%ld to r%ld"), 6080 old_rev, new_rev); 6081 } 6082 else 6083 { 6084 if (new_node_kind == svn_node_dir) 6085 return apr_psprintf(result_pool, 6086 _("Changes destined for a directory arrived " 6087 "via the following revisions during backwards " 6088 "update from r%ld to r%ld"), 6089 old_rev, new_rev); 6090 else if (new_node_kind == svn_node_file || 6091 new_node_kind == svn_node_symlink) 6092 return apr_psprintf(result_pool, 6093 _("Changes destined for a file arrived " 6094 "via the following revisions during backwards " 6095 "update from r%ld to r%ld"), 6096 old_rev, new_rev); 6097 else 6098 return apr_psprintf(result_pool, 6099 _("Changes from the following revisions arrived " 6100 "during backwards update from r%ld to r%ld"), 6101 old_rev, new_rev); 6102 } 6103} 6104 6105static const char * 6106describe_incoming_edit_upon_switch(const char *new_repos_relpath, 6107 svn_revnum_t new_rev, 6108 svn_node_kind_t new_node_kind, 6109 apr_pool_t *result_pool) 6110{ 6111 if (new_node_kind == svn_node_dir) 6112 return apr_psprintf(result_pool, 6113 _("Changes destined for a directory arrived via " 6114 "the following revisions during switch to\n" 6115 "'^/%s@r%ld'"), 6116 new_repos_relpath, new_rev); 6117 else if (new_node_kind == svn_node_file || 6118 new_node_kind == svn_node_symlink) 6119 return apr_psprintf(result_pool, 6120 _("Changes destined for a directory arrived via " 6121 "the following revisions during switch to\n" 6122 "'^/%s@r%ld'"), 6123 new_repos_relpath, new_rev); 6124 else 6125 return apr_psprintf(result_pool, 6126 _("Changes from the following revisions arrived " 6127 "during switch to\n'^/%s@r%ld'"), 6128 new_repos_relpath, new_rev); 6129} 6130 6131/* Return a string showing the list of revisions in EDITS, ensuring 6132 * the string won't grow too large for display. */ 6133static const char * 6134describe_incoming_edit_list_modified_revs(apr_array_header_t *edits, 6135 apr_pool_t *result_pool) 6136{ 6137 int num_revs_to_skip; 6138 static const int min_revs_for_skipping = 5; 6139 static const int max_revs_to_display = 8; 6140 const char *s = ""; 6141 int i; 6142 6143 if (edits->nelts == 0) 6144 return _(" (no revisions found)"); 6145 6146 if (edits->nelts <= max_revs_to_display) 6147 num_revs_to_skip = 0; 6148 else 6149 { 6150 /* Check if we should insert a placeholder for some revisions because 6151 * the string would grow too long for display otherwise. */ 6152 num_revs_to_skip = edits->nelts - max_revs_to_display; 6153 if (num_revs_to_skip < min_revs_for_skipping) 6154 { 6155 /* Don't bother with the placeholder. Just list all revisions. */ 6156 num_revs_to_skip = 0; 6157 } 6158 } 6159 6160 for (i = 0; i < edits->nelts; i++) 6161 { 6162 struct conflict_tree_incoming_edit_details *details; 6163 6164 details = APR_ARRAY_IDX(edits, i, 6165 struct conflict_tree_incoming_edit_details *); 6166 if (num_revs_to_skip > 0) 6167 { 6168 /* Insert a placeholder for revisions falling into the middle of 6169 * the range so we'll get something that looks like: 6170 * 1, 2, 3, 4, 5 [ placeholder ] 95, 96, 97, 98, 99 */ 6171 if (i < max_revs_to_display / 2) 6172 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s, 6173 details->rev, details->author, 6174 i < edits->nelts - 1 ? "," : ""); 6175 else if (i >= max_revs_to_display / 2 && 6176 i < edits->nelts - (max_revs_to_display / 2)) 6177 continue; 6178 else 6179 { 6180 if (i == edits->nelts - (max_revs_to_display / 2)) 6181 s = apr_psprintf(result_pool, 6182 Q_("%s\n [%d revision omitted for " 6183 "brevity],\n", 6184 "%s\n [%d revisions omitted for " 6185 "brevity],\n", 6186 num_revs_to_skip), 6187 s, num_revs_to_skip); 6188 6189 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s, 6190 details->rev, details->author, 6191 i < edits->nelts - 1 ? "," : ""); 6192 } 6193 } 6194 else 6195 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s, 6196 details->rev, details->author, 6197 i < edits->nelts - 1 ? "," : ""); 6198 } 6199 6200 return s; 6201} 6202 6203/* Implements tree_conflict_get_description_func_t. */ 6204static svn_error_t * 6205conflict_tree_get_description_incoming_edit( 6206 const char **incoming_change_description, 6207 svn_client_conflict_t *conflict, 6208 svn_client_ctx_t *ctx, 6209 apr_pool_t *result_pool, 6210 apr_pool_t *scratch_pool) 6211{ 6212 const char *action; 6213 svn_wc_operation_t conflict_operation; 6214 const char *old_repos_relpath; 6215 svn_revnum_t old_rev; 6216 svn_node_kind_t old_node_kind; 6217 const char *new_repos_relpath; 6218 svn_revnum_t new_rev; 6219 svn_node_kind_t new_node_kind; 6220 apr_array_header_t *edits; 6221 6222 if (conflict->tree_conflict_incoming_details == NULL) 6223 return svn_error_trace(conflict_tree_get_incoming_description_generic( 6224 incoming_change_description, conflict, ctx, 6225 result_pool, scratch_pool)); 6226 6227 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 6228 &old_repos_relpath, &old_rev, &old_node_kind, conflict, 6229 scratch_pool, scratch_pool)); 6230 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 6231 &new_repos_relpath, &new_rev, &new_node_kind, conflict, 6232 scratch_pool, scratch_pool)); 6233 6234 conflict_operation = svn_client_conflict_get_operation(conflict); 6235 6236 edits = conflict->tree_conflict_incoming_details; 6237 6238 if (conflict_operation == svn_wc_operation_update) 6239 action = describe_incoming_edit_upon_update(old_rev, new_rev, 6240 old_node_kind, new_node_kind, 6241 scratch_pool); 6242 else if (conflict_operation == svn_wc_operation_switch) 6243 action = describe_incoming_edit_upon_switch(new_repos_relpath, new_rev, 6244 new_node_kind, scratch_pool); 6245 else if (conflict_operation == svn_wc_operation_merge) 6246 { 6247 /* Handle merge inline because it returns early sometimes. */ 6248 if (old_rev < new_rev) 6249 { 6250 if (old_rev + 1 == new_rev) 6251 { 6252 if (new_node_kind == svn_node_dir) 6253 action = apr_psprintf(scratch_pool, 6254 _("Changes destined for a directory " 6255 "arrived during merge of\n" 6256 "'^/%s:%ld'."), 6257 new_repos_relpath, new_rev); 6258 else if (new_node_kind == svn_node_file || 6259 new_node_kind == svn_node_symlink) 6260 action = apr_psprintf(scratch_pool, 6261 _("Changes destined for a file " 6262 "arrived during merge of\n" 6263 "'^/%s:%ld'."), 6264 new_repos_relpath, new_rev); 6265 else 6266 action = apr_psprintf(scratch_pool, 6267 _("Changes arrived during merge of\n" 6268 "'^/%s:%ld'."), 6269 new_repos_relpath, new_rev); 6270 6271 *incoming_change_description = apr_pstrdup(result_pool, action); 6272 6273 return SVN_NO_ERROR; 6274 } 6275 else 6276 { 6277 if (new_node_kind == svn_node_dir) 6278 action = apr_psprintf(scratch_pool, 6279 _("Changes destined for a directory " 6280 "arrived via the following revisions " 6281 "during merge of\n'^/%s:%ld-%ld'"), 6282 new_repos_relpath, old_rev + 1, new_rev); 6283 else if (new_node_kind == svn_node_file || 6284 new_node_kind == svn_node_symlink) 6285 action = apr_psprintf(scratch_pool, 6286 _("Changes destined for a file " 6287 "arrived via the following revisions " 6288 "during merge of\n'^/%s:%ld-%ld'"), 6289 new_repos_relpath, old_rev + 1, new_rev); 6290 else 6291 action = apr_psprintf(scratch_pool, 6292 _("Changes from the following revisions " 6293 "arrived during merge of\n" 6294 "'^/%s:%ld-%ld'"), 6295 new_repos_relpath, old_rev + 1, new_rev); 6296 } 6297 } 6298 else 6299 { 6300 if (new_rev + 1 == old_rev) 6301 { 6302 if (new_node_kind == svn_node_dir) 6303 action = apr_psprintf(scratch_pool, 6304 _("Changes destined for a directory " 6305 "arrived during reverse-merge of\n" 6306 "'^/%s:%ld'."), 6307 new_repos_relpath, old_rev); 6308 else if (new_node_kind == svn_node_file || 6309 new_node_kind == svn_node_symlink) 6310 action = apr_psprintf(scratch_pool, 6311 _("Changes destined for a file " 6312 "arrived during reverse-merge of\n" 6313 "'^/%s:%ld'."), 6314 new_repos_relpath, old_rev); 6315 else 6316 action = apr_psprintf(scratch_pool, 6317 _("Changes arrived during reverse-merge " 6318 "of\n'^/%s:%ld'."), 6319 new_repos_relpath, old_rev); 6320 6321 *incoming_change_description = apr_pstrdup(result_pool, action); 6322 6323 return SVN_NO_ERROR; 6324 } 6325 else 6326 { 6327 if (new_node_kind == svn_node_dir) 6328 action = apr_psprintf(scratch_pool, 6329 _("Changes destined for a directory " 6330 "arrived via the following revisions " 6331 "during reverse-merge of\n" 6332 "'^/%s:%ld-%ld'"), 6333 new_repos_relpath, new_rev + 1, old_rev); 6334 else if (new_node_kind == svn_node_file || 6335 new_node_kind == svn_node_symlink) 6336 action = apr_psprintf(scratch_pool, 6337 _("Changes destined for a file " 6338 "arrived via the following revisions " 6339 "during reverse-merge of\n" 6340 "'^/%s:%ld-%ld'"), 6341 new_repos_relpath, new_rev + 1, old_rev); 6342 6343 else 6344 action = apr_psprintf(scratch_pool, 6345 _("Changes from the following revisions " 6346 "arrived during reverse-merge of\n" 6347 "'^/%s:%ld-%ld'"), 6348 new_repos_relpath, new_rev + 1, old_rev); 6349 } 6350 } 6351 } 6352 6353 action = apr_psprintf(scratch_pool, "%s:\n%s", action, 6354 describe_incoming_edit_list_modified_revs( 6355 edits, scratch_pool)); 6356 *incoming_change_description = apr_pstrdup(result_pool, action); 6357 6358 return SVN_NO_ERROR; 6359} 6360 6361svn_error_t * 6362svn_client_conflict_tree_get_description( 6363 const char **incoming_change_description, 6364 const char **local_change_description, 6365 svn_client_conflict_t *conflict, 6366 svn_client_ctx_t *ctx, 6367 apr_pool_t *result_pool, 6368 apr_pool_t *scratch_pool) 6369{ 6370 SVN_ERR(conflict->tree_conflict_get_incoming_description_func( 6371 incoming_change_description, 6372 conflict, ctx, result_pool, scratch_pool)); 6373 6374 SVN_ERR(conflict->tree_conflict_get_local_description_func( 6375 local_change_description, 6376 conflict, ctx, result_pool, scratch_pool)); 6377 6378 return SVN_NO_ERROR; 6379} 6380 6381void 6382svn_client_conflict_option_set_merged_propval( 6383 svn_client_conflict_option_t *option, 6384 const svn_string_t *merged_propval) 6385{ 6386 option->type_data.prop.merged_propval = svn_string_dup(merged_propval, 6387 option->pool); 6388} 6389 6390/* Implements conflict_option_resolve_func_t. */ 6391static svn_error_t * 6392resolve_postpone(svn_client_conflict_option_t *option, 6393 svn_client_conflict_t *conflict, 6394 svn_client_ctx_t *ctx, 6395 apr_pool_t *scratch_pool) 6396{ 6397 return SVN_NO_ERROR; /* Nothing to do. */ 6398} 6399 6400/* Implements conflict_option_resolve_func_t. */ 6401static svn_error_t * 6402resolve_text_conflict(svn_client_conflict_option_t *option, 6403 svn_client_conflict_t *conflict, 6404 svn_client_ctx_t *ctx, 6405 apr_pool_t *scratch_pool) 6406{ 6407 svn_client_conflict_option_id_t option_id; 6408 const char *local_abspath; 6409 const char *lock_abspath; 6410 svn_wc_conflict_choice_t conflict_choice; 6411 svn_error_t *err; 6412 6413 option_id = svn_client_conflict_option_get_id(option); 6414 conflict_choice = conflict_option_id_to_wc_conflict_choice(option_id); 6415 local_abspath = svn_client_conflict_get_local_abspath(conflict); 6416 6417 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 6418 local_abspath, 6419 scratch_pool, scratch_pool)); 6420 err = svn_wc__conflict_text_mark_resolved(ctx->wc_ctx, 6421 local_abspath, 6422 conflict_choice, 6423 ctx->cancel_func, 6424 ctx->cancel_baton, 6425 ctx->notify_func2, 6426 ctx->notify_baton2, 6427 scratch_pool); 6428 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 6429 lock_abspath, 6430 scratch_pool)); 6431 svn_io_sleep_for_timestamps(local_abspath, scratch_pool); 6432 SVN_ERR(err); 6433 6434 conflict->resolution_text = option_id; 6435 6436 return SVN_NO_ERROR; 6437} 6438 6439/* Implements conflict_option_resolve_func_t. */ 6440static svn_error_t * 6441resolve_prop_conflict(svn_client_conflict_option_t *option, 6442 svn_client_conflict_t *conflict, 6443 svn_client_ctx_t *ctx, 6444 apr_pool_t *scratch_pool) 6445{ 6446 svn_client_conflict_option_id_t option_id; 6447 svn_wc_conflict_choice_t conflict_choice; 6448 const char *local_abspath; 6449 const char *lock_abspath; 6450 const char *propname = option->type_data.prop.propname; 6451 svn_error_t *err; 6452 const svn_string_t *merged_value; 6453 6454 option_id = svn_client_conflict_option_get_id(option); 6455 conflict_choice = conflict_option_id_to_wc_conflict_choice(option_id); 6456 local_abspath = svn_client_conflict_get_local_abspath(conflict); 6457 6458 if (option_id == svn_client_conflict_option_merged_text) 6459 merged_value = option->type_data.prop.merged_propval; 6460 else 6461 merged_value = NULL; 6462 6463 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 6464 local_abspath, 6465 scratch_pool, scratch_pool)); 6466 err = svn_wc__conflict_prop_mark_resolved(ctx->wc_ctx, local_abspath, 6467 propname, conflict_choice, 6468 merged_value, 6469 ctx->notify_func2, 6470 ctx->notify_baton2, 6471 scratch_pool); 6472 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 6473 lock_abspath, 6474 scratch_pool)); 6475 svn_io_sleep_for_timestamps(local_abspath, scratch_pool); 6476 SVN_ERR(err); 6477 6478 if (propname[0] == '\0') 6479 { 6480 apr_hash_index_t *hi; 6481 6482 /* All properties have been resolved to the same option. */ 6483 for (hi = apr_hash_first(scratch_pool, conflict->prop_conflicts); 6484 hi; 6485 hi = apr_hash_next(hi)) 6486 { 6487 const char *this_propname = apr_hash_this_key(hi); 6488 6489 svn_hash_sets(conflict->resolved_props, 6490 apr_pstrdup(apr_hash_pool_get(conflict->resolved_props), 6491 this_propname), 6492 option); 6493 svn_hash_sets(conflict->prop_conflicts, this_propname, NULL); 6494 } 6495 6496 conflict->legacy_prop_conflict_propname = NULL; 6497 } 6498 else 6499 { 6500 svn_hash_sets(conflict->resolved_props, 6501 apr_pstrdup(apr_hash_pool_get(conflict->resolved_props), 6502 propname), 6503 option); 6504 svn_hash_sets(conflict->prop_conflicts, propname, NULL); 6505 6506 if (apr_hash_count(conflict->prop_conflicts) > 0) 6507 conflict->legacy_prop_conflict_propname = 6508 apr_hash_this_key(apr_hash_first(scratch_pool, 6509 conflict->prop_conflicts)); 6510 else 6511 conflict->legacy_prop_conflict_propname = NULL; 6512 } 6513 6514 return SVN_NO_ERROR; 6515} 6516 6517/* Implements conflict_option_resolve_func_t. */ 6518static svn_error_t * 6519resolve_accept_current_wc_state(svn_client_conflict_option_t *option, 6520 svn_client_conflict_t *conflict, 6521 svn_client_ctx_t *ctx, 6522 apr_pool_t *scratch_pool) 6523{ 6524 svn_client_conflict_option_id_t option_id; 6525 const char *local_abspath; 6526 const char *lock_abspath; 6527 svn_error_t *err; 6528 6529 option_id = svn_client_conflict_option_get_id(option); 6530 local_abspath = svn_client_conflict_get_local_abspath(conflict); 6531 6532 if (option_id != svn_client_conflict_option_accept_current_wc_state) 6533 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6534 _("Tree conflict on '%s' can only be resolved " 6535 "to the current working copy state"), 6536 svn_dirent_local_style(local_abspath, 6537 scratch_pool)); 6538 6539 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 6540 local_abspath, 6541 scratch_pool, scratch_pool)); 6542 6543 /* Resolve to current working copy state. */ 6544 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool); 6545 6546 /* svn_wc__del_tree_conflict doesn't handle notification for us */ 6547 if (ctx->notify_func2) 6548 ctx->notify_func2(ctx->notify_baton2, 6549 svn_wc_create_notify(local_abspath, 6550 svn_wc_notify_resolved_tree, 6551 scratch_pool), 6552 scratch_pool); 6553 6554 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 6555 lock_abspath, 6556 scratch_pool)); 6557 SVN_ERR(err); 6558 6559 conflict->resolution_tree = option_id; 6560 6561 return SVN_NO_ERROR; 6562} 6563 6564/* Implements conflict_option_resolve_func_t. */ 6565static svn_error_t * 6566resolve_update_break_moved_away(svn_client_conflict_option_t *option, 6567 svn_client_conflict_t *conflict, 6568 svn_client_ctx_t *ctx, 6569 apr_pool_t *scratch_pool) 6570{ 6571 const char *local_abspath; 6572 const char *lock_abspath; 6573 svn_error_t *err; 6574 6575 local_abspath = svn_client_conflict_get_local_abspath(conflict); 6576 6577 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 6578 local_abspath, 6579 scratch_pool, scratch_pool)); 6580 err = svn_wc__conflict_tree_update_break_moved_away(ctx->wc_ctx, 6581 local_abspath, 6582 ctx->cancel_func, 6583 ctx->cancel_baton, 6584 ctx->notify_func2, 6585 ctx->notify_baton2, 6586 scratch_pool); 6587 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 6588 lock_abspath, 6589 scratch_pool)); 6590 SVN_ERR(err); 6591 6592 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 6593 6594 return SVN_NO_ERROR; 6595} 6596 6597/* Implements conflict_option_resolve_func_t. */ 6598static svn_error_t * 6599resolve_update_raise_moved_away(svn_client_conflict_option_t *option, 6600 svn_client_conflict_t *conflict, 6601 svn_client_ctx_t *ctx, 6602 apr_pool_t *scratch_pool) 6603{ 6604 const char *local_abspath; 6605 const char *lock_abspath; 6606 svn_error_t *err; 6607 6608 local_abspath = svn_client_conflict_get_local_abspath(conflict); 6609 6610 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 6611 local_abspath, 6612 scratch_pool, scratch_pool)); 6613 err = svn_wc__conflict_tree_update_raise_moved_away(ctx->wc_ctx, 6614 local_abspath, 6615 ctx->cancel_func, 6616 ctx->cancel_baton, 6617 ctx->notify_func2, 6618 ctx->notify_baton2, 6619 scratch_pool); 6620 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 6621 lock_abspath, 6622 scratch_pool)); 6623 SVN_ERR(err); 6624 6625 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 6626 6627 return SVN_NO_ERROR; 6628} 6629 6630/* Implements conflict_option_resolve_func_t. */ 6631static svn_error_t * 6632resolve_update_moved_away_node(svn_client_conflict_option_t *option, 6633 svn_client_conflict_t *conflict, 6634 svn_client_ctx_t *ctx, 6635 apr_pool_t *scratch_pool) 6636{ 6637 const char *local_abspath; 6638 const char *lock_abspath; 6639 svn_error_t *err; 6640 6641 local_abspath = svn_client_conflict_get_local_abspath(conflict); 6642 6643 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 6644 local_abspath, 6645 scratch_pool, scratch_pool)); 6646 err = svn_wc__conflict_tree_update_moved_away_node(ctx->wc_ctx, 6647 local_abspath, 6648 ctx->cancel_func, 6649 ctx->cancel_baton, 6650 ctx->notify_func2, 6651 ctx->notify_baton2, 6652 scratch_pool); 6653 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 6654 lock_abspath, 6655 scratch_pool)); 6656 svn_io_sleep_for_timestamps(local_abspath, scratch_pool); 6657 SVN_ERR(err); 6658 6659 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 6660 6661 return SVN_NO_ERROR; 6662} 6663 6664/* Verify the local working copy state matches what we expect when an 6665 * incoming add vs add tree conflict exists after an update operation. 6666 * We assume the update operation leaves the working copy in a state which 6667 * prefers the local change and cancels the incoming addition. 6668 * Run a quick sanity check and error out if it looks as if the 6669 * working copy was modified since, even though it's not easy to make 6670 * such modifications without also clearing the conflict marker. */ 6671static svn_error_t * 6672verify_local_state_for_incoming_add_upon_update( 6673 svn_client_conflict_t *conflict, 6674 svn_client_conflict_option_t *option, 6675 svn_client_ctx_t *ctx, 6676 apr_pool_t *scratch_pool) 6677{ 6678 const char *local_abspath; 6679 svn_client_conflict_option_id_t option_id; 6680 const char *wcroot_abspath; 6681 svn_wc_operation_t operation; 6682 const char *incoming_new_repos_relpath; 6683 svn_revnum_t incoming_new_pegrev; 6684 svn_node_kind_t incoming_new_kind; 6685 const char *base_repos_relpath; 6686 svn_revnum_t base_rev; 6687 svn_node_kind_t base_kind; 6688 const char *local_style_relpath; 6689 svn_boolean_t is_added; 6690 svn_error_t *err; 6691 6692 local_abspath = svn_client_conflict_get_local_abspath(conflict); 6693 option_id = svn_client_conflict_option_get_id(option); 6694 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 6695 local_abspath, scratch_pool, 6696 scratch_pool)); 6697 operation = svn_client_conflict_get_operation(conflict); 6698 SVN_ERR_ASSERT(operation == svn_wc_operation_update); 6699 6700 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 6701 &incoming_new_repos_relpath, &incoming_new_pegrev, 6702 &incoming_new_kind, conflict, scratch_pool, 6703 scratch_pool)); 6704 6705 local_style_relpath = svn_dirent_local_style( 6706 svn_dirent_skip_ancestor(wcroot_abspath, 6707 local_abspath), 6708 scratch_pool); 6709 6710 /* Check if a local addition addition replaces the incoming new node. */ 6711 err = svn_wc__node_get_base(&base_kind, &base_rev, &base_repos_relpath, 6712 NULL, NULL, NULL, ctx->wc_ctx, local_abspath, 6713 FALSE, scratch_pool, scratch_pool); 6714 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 6715 { 6716 if (option_id == svn_client_conflict_option_incoming_add_ignore) 6717 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err, 6718 _("Cannot resolve tree conflict on '%s' " 6719 "(expected a base node but found none)"), 6720 local_style_relpath); 6721 else if (option_id == 6722 svn_client_conflict_option_incoming_added_dir_replace) 6723 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err, 6724 _("Cannot resolve tree conflict on '%s' " 6725 "(expected a base node but found none)"), 6726 local_style_relpath); 6727 else 6728 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err, 6729 _("Unexpected option id '%d'"), option_id); 6730 } 6731 else if (err) 6732 return svn_error_trace(err); 6733 6734 if (base_kind != incoming_new_kind) 6735 { 6736 if (option_id == svn_client_conflict_option_incoming_add_ignore) 6737 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6738 _("Cannot resolve tree conflict on '%s' " 6739 "(expected base node kind '%s', " 6740 "but found '%s')"), 6741 local_style_relpath, 6742 svn_node_kind_to_word(incoming_new_kind), 6743 svn_node_kind_to_word(base_kind)); 6744 else if (option_id == 6745 svn_client_conflict_option_incoming_added_dir_replace) 6746 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6747 _("Cannot resolve tree conflict on '%s' " 6748 "(expected base node kind '%s', " 6749 "but found '%s')"), 6750 local_style_relpath, 6751 svn_node_kind_to_word(incoming_new_kind), 6752 svn_node_kind_to_word(base_kind)); 6753 else 6754 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6755 _("Unexpected option id '%d'"), option_id); 6756 } 6757 6758 if (strcmp(base_repos_relpath, incoming_new_repos_relpath) != 0 || 6759 base_rev != incoming_new_pegrev) 6760 { 6761 if (option_id == svn_client_conflict_option_incoming_add_ignore) 6762 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6763 _("Cannot resolve tree conflict on '%s' " 6764 "(expected base node from '^/%s@%ld', " 6765 "but found '^/%s@%ld')"), 6766 local_style_relpath, 6767 incoming_new_repos_relpath, 6768 incoming_new_pegrev, 6769 base_repos_relpath, base_rev); 6770 else if (option_id == 6771 svn_client_conflict_option_incoming_added_dir_replace) 6772 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6773 _("Cannot resolve tree conflict on '%s' " 6774 "(expected base node from '^/%s@%ld', " 6775 "but found '^/%s@%ld')"), 6776 local_style_relpath, 6777 incoming_new_repos_relpath, 6778 incoming_new_pegrev, 6779 base_repos_relpath, base_rev); 6780 else 6781 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6782 _("Unexpected option id '%d'"), option_id); 6783 } 6784 6785 SVN_ERR(svn_wc__node_is_added(&is_added, ctx->wc_ctx, local_abspath, 6786 scratch_pool)); 6787 if (!is_added) 6788 { 6789 if (option_id == svn_client_conflict_option_incoming_add_ignore) 6790 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6791 _("Cannot resolve tree conflict on '%s' " 6792 "(expected an added item, but the item " 6793 "is not added)"), 6794 local_style_relpath); 6795 6796 else if (option_id == 6797 svn_client_conflict_option_incoming_added_dir_replace) 6798 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6799 _("Cannot resolve tree conflict on '%s' " 6800 "(expected an added item, but the item " 6801 "is not added)"), 6802 local_style_relpath); 6803 else 6804 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 6805 _("Unexpected option id '%d'"), option_id); 6806 } 6807 6808 return SVN_NO_ERROR; 6809} 6810 6811 6812/* Implements conflict_option_resolve_func_t. */ 6813static svn_error_t * 6814resolve_incoming_add_ignore(svn_client_conflict_option_t *option, 6815 svn_client_conflict_t *conflict, 6816 svn_client_ctx_t *ctx, 6817 apr_pool_t *scratch_pool) 6818{ 6819 const char *local_abspath; 6820 const char *lock_abspath; 6821 svn_wc_operation_t operation; 6822 svn_error_t *err; 6823 6824 local_abspath = svn_client_conflict_get_local_abspath(conflict); 6825 operation = svn_client_conflict_get_operation(conflict); 6826 6827 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 6828 local_abspath, 6829 scratch_pool, scratch_pool)); 6830 6831 if (operation == svn_wc_operation_update) 6832 { 6833 err = verify_local_state_for_incoming_add_upon_update(conflict, option, 6834 ctx, scratch_pool); 6835 if (err) 6836 goto unlock_wc; 6837 } 6838 6839 /* All other options for this conflict actively fetch the incoming 6840 * new node. We can ignore the incoming new node by doing nothing. */ 6841 6842 /* Resolve to current working copy state. */ 6843 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool); 6844 6845 /* svn_wc__del_tree_conflict doesn't handle notification for us */ 6846 if (ctx->notify_func2) 6847 ctx->notify_func2(ctx->notify_baton2, 6848 svn_wc_create_notify(local_abspath, 6849 svn_wc_notify_resolved_tree, 6850 scratch_pool), 6851 scratch_pool); 6852 6853unlock_wc: 6854 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 6855 lock_abspath, 6856 scratch_pool)); 6857 SVN_ERR(err); 6858 6859 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 6860 6861 return SVN_NO_ERROR; 6862} 6863 6864/* Delete entry and wc props from a set of properties. */ 6865static void 6866filter_props(apr_hash_t *props, apr_pool_t *scratch_pool) 6867{ 6868 apr_hash_index_t *hi; 6869 6870 for (hi = apr_hash_first(scratch_pool, props); 6871 hi != NULL; 6872 hi = apr_hash_next(hi)) 6873 { 6874 const char *propname = apr_hash_this_key(hi); 6875 6876 if (!svn_wc_is_normal_prop(propname)) 6877 svn_hash_sets(props, propname, NULL); 6878 } 6879} 6880 6881/* Implements conflict_option_resolve_func_t. */ 6882static svn_error_t * 6883resolve_merge_incoming_added_file_text_update( 6884 svn_client_conflict_option_t *option, 6885 svn_client_conflict_t *conflict, 6886 svn_client_ctx_t *ctx, 6887 apr_pool_t *scratch_pool) 6888{ 6889 const char *wc_tmpdir; 6890 const char *local_abspath; 6891 const char *lock_abspath; 6892 svn_wc_merge_outcome_t merge_content_outcome; 6893 svn_wc_notify_state_t merge_props_outcome; 6894 const char *empty_file_abspath; 6895 const char *working_file_tmp_abspath; 6896 svn_stream_t *working_file_stream; 6897 svn_stream_t *working_file_tmp_stream; 6898 apr_hash_t *working_props; 6899 apr_array_header_t *propdiffs; 6900 svn_error_t *err; 6901 svn_wc_conflict_reason_t local_change; 6902 6903 local_abspath = svn_client_conflict_get_local_abspath(conflict); 6904 local_change = svn_client_conflict_get_local_change(conflict); 6905 6906 /* Set up tempory storage for the working version of file. */ 6907 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath, 6908 scratch_pool, scratch_pool)); 6909 SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream, 6910 &working_file_tmp_abspath, wc_tmpdir, 6911 /* Don't delete automatically! */ 6912 svn_io_file_del_none, 6913 scratch_pool, scratch_pool)); 6914 6915 if (local_change == svn_wc_conflict_reason_unversioned) 6916 { 6917 /* Copy the unversioned file to temporary storage. */ 6918 SVN_ERR(svn_stream_open_readonly(&working_file_stream, local_abspath, 6919 scratch_pool, scratch_pool)); 6920 /* Unversioned files have no properties. */ 6921 working_props = apr_hash_make(scratch_pool); 6922 } 6923 else 6924 { 6925 /* Copy the detranslated working file to temporary storage. */ 6926 SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx, 6927 local_abspath, local_abspath, 6928 SVN_WC_TRANSLATE_TO_NF, 6929 scratch_pool, scratch_pool)); 6930 /* Get a copy of the working file's properties. */ 6931 SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath, 6932 scratch_pool, scratch_pool)); 6933 filter_props(working_props, scratch_pool); 6934 } 6935 6936 SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream, 6937 ctx->cancel_func, ctx->cancel_baton, 6938 scratch_pool)); 6939 6940 /* Create an empty file as fake "merge-base" for the two added files. 6941 * The files are not ancestrally related so this is the best we can do. */ 6942 SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL, 6943 svn_io_file_del_on_pool_cleanup, 6944 scratch_pool, scratch_pool)); 6945 6946 /* Create a property diff which shows all props as added. */ 6947 SVN_ERR(svn_prop_diffs(&propdiffs, working_props, 6948 apr_hash_make(scratch_pool), scratch_pool)); 6949 6950 /* ### The following WC modifications should be atomic. */ 6951 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 6952 local_abspath, 6953 scratch_pool, scratch_pool)); 6954 6955 /* Revert the path in order to restore the repository's line of 6956 * history, which is part of the BASE tree. This revert operation 6957 * is why are being careful about not losing the temporary copy. */ 6958 err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_empty, 6959 FALSE, NULL, TRUE, FALSE, 6960 TRUE /*added_keep_local*/, 6961 NULL, NULL, /* no cancellation */ 6962 ctx->notify_func2, ctx->notify_baton2, 6963 scratch_pool); 6964 if (err) 6965 goto unlock_wc; 6966 6967 /* Perform the file merge. ### Merge into tempfile and then rename on top? */ 6968 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, 6969 ctx->wc_ctx, empty_file_abspath, 6970 working_file_tmp_abspath, local_abspath, 6971 NULL, NULL, NULL, /* labels */ 6972 NULL, NULL, /* conflict versions */ 6973 FALSE, /* dry run */ 6974 NULL, NULL, /* diff3_cmd, merge_options */ 6975 NULL, propdiffs, 6976 NULL, NULL, /* conflict func/baton */ 6977 NULL, NULL, /* don't allow user to cancel here */ 6978 scratch_pool); 6979 6980unlock_wc: 6981 if (err) 6982 err = svn_error_quick_wrapf( 6983 err, _("If needed, a backup copy of '%s' can be found at '%s'"), 6984 svn_dirent_local_style(local_abspath, scratch_pool), 6985 svn_dirent_local_style(working_file_tmp_abspath, scratch_pool)); 6986 err = svn_error_compose_create(err, 6987 svn_wc__release_write_lock(ctx->wc_ctx, 6988 lock_abspath, 6989 scratch_pool)); 6990 svn_io_sleep_for_timestamps(local_abspath, scratch_pool); 6991 SVN_ERR(err); 6992 6993 if (ctx->notify_func2) 6994 { 6995 svn_wc_notify_t *notify; 6996 6997 /* Tell the world about the file merge that just happened. */ 6998 notify = svn_wc_create_notify(local_abspath, 6999 svn_wc_notify_update_update, 7000 scratch_pool); 7001 if (merge_content_outcome == svn_wc_merge_conflict) 7002 notify->content_state = svn_wc_notify_state_conflicted; 7003 else 7004 notify->content_state = svn_wc_notify_state_merged; 7005 notify->prop_state = merge_props_outcome; 7006 notify->kind = svn_node_file; 7007 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 7008 7009 /* And also about the successfully resolved tree conflict. */ 7010 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree, 7011 scratch_pool); 7012 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 7013 } 7014 7015 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 7016 7017 /* All is good -- remove temporary copy of the working file. */ 7018 SVN_ERR(svn_io_remove_file2(working_file_tmp_abspath, TRUE, scratch_pool)); 7019 7020 return SVN_NO_ERROR; 7021} 7022 7023/* Implements conflict_option_resolve_func_t. */ 7024static svn_error_t * 7025resolve_merge_incoming_added_file_text_merge( 7026 svn_client_conflict_option_t *option, 7027 svn_client_conflict_t *conflict, 7028 svn_client_ctx_t *ctx, 7029 apr_pool_t *scratch_pool) 7030{ 7031 svn_ra_session_t *ra_session; 7032 const char *url; 7033 const char *corrected_url; 7034 const char *repos_root_url; 7035 const char *wc_tmpdir; 7036 const char *incoming_new_repos_relpath; 7037 svn_revnum_t incoming_new_pegrev; 7038 const char *local_abspath; 7039 const char *lock_abspath; 7040 svn_wc_merge_outcome_t merge_content_outcome; 7041 svn_wc_notify_state_t merge_props_outcome; 7042 apr_file_t *incoming_new_file; 7043 const char *incoming_new_tmp_abspath; 7044 const char *empty_file_abspath; 7045 svn_stream_t *incoming_new_stream; 7046 apr_hash_t *incoming_new_props; 7047 apr_array_header_t *propdiffs; 7048 svn_error_t *err; 7049 7050 local_abspath = svn_client_conflict_get_local_abspath(conflict); 7051 7052 /* Set up temporary storage for the repository version of file. */ 7053 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath, 7054 scratch_pool, scratch_pool)); 7055 SVN_ERR(svn_io_open_unique_file3(&incoming_new_file, 7056 &incoming_new_tmp_abspath, wc_tmpdir, 7057 svn_io_file_del_on_pool_cleanup, 7058 scratch_pool, scratch_pool)); 7059 incoming_new_stream = svn_stream_from_aprfile2(incoming_new_file, TRUE, 7060 scratch_pool); 7061 7062 /* Fetch the incoming added file from the repository. */ 7063 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 7064 &incoming_new_repos_relpath, &incoming_new_pegrev, 7065 NULL, conflict, scratch_pool, 7066 scratch_pool)); 7067 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 7068 conflict, scratch_pool, 7069 scratch_pool)); 7070 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath, 7071 scratch_pool); 7072 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 7073 url, NULL, NULL, FALSE, FALSE, 7074 ctx, scratch_pool, 7075 scratch_pool)); 7076 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, 7077 incoming_new_stream, NULL, /* fetched_rev */ 7078 &incoming_new_props, scratch_pool)); 7079 7080 /* Flush file to disk. */ 7081 SVN_ERR(svn_stream_close(incoming_new_stream)); 7082 SVN_ERR(svn_io_file_flush(incoming_new_file, scratch_pool)); 7083 7084 filter_props(incoming_new_props, scratch_pool); 7085 7086 /* Create an empty file as fake "merge-base" for the two added files. 7087 * The files are not ancestrally related so this is the best we can do. */ 7088 SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL, 7089 svn_io_file_del_on_pool_cleanup, 7090 scratch_pool, scratch_pool)); 7091 7092 /* Create a property diff which shows all props as added. */ 7093 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_new_props, 7094 apr_hash_make(scratch_pool), scratch_pool)); 7095 7096 /* ### The following WC modifications should be atomic. */ 7097 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 7098 local_abspath, 7099 scratch_pool, scratch_pool)); 7100 /* Resolve to current working copy state. svn_wc_merge5() requires this. */ 7101 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool); 7102 if (err) 7103 return svn_error_compose_create(err, 7104 svn_wc__release_write_lock(ctx->wc_ctx, 7105 lock_abspath, 7106 scratch_pool)); 7107 /* Perform the file merge. ### Merge into tempfile and then rename on top? */ 7108 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, 7109 ctx->wc_ctx, empty_file_abspath, 7110 incoming_new_tmp_abspath, local_abspath, 7111 NULL, NULL, NULL, /* labels */ 7112 NULL, NULL, /* conflict versions */ 7113 FALSE, /* dry run */ 7114 NULL, NULL, /* diff3_cmd, merge_options */ 7115 NULL, propdiffs, 7116 NULL, NULL, /* conflict func/baton */ 7117 NULL, NULL, /* don't allow user to cancel here */ 7118 scratch_pool); 7119 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 7120 lock_abspath, 7121 scratch_pool)); 7122 svn_io_sleep_for_timestamps(local_abspath, scratch_pool); 7123 SVN_ERR(err); 7124 7125 if (ctx->notify_func2) 7126 { 7127 svn_wc_notify_t *notify; 7128 7129 /* Tell the world about the file merge that just happened. */ 7130 notify = svn_wc_create_notify(local_abspath, 7131 svn_wc_notify_update_update, 7132 scratch_pool); 7133 if (merge_content_outcome == svn_wc_merge_conflict) 7134 notify->content_state = svn_wc_notify_state_conflicted; 7135 else 7136 notify->content_state = svn_wc_notify_state_merged; 7137 notify->prop_state = merge_props_outcome; 7138 notify->kind = svn_node_file; 7139 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 7140 7141 /* And also about the successfully resolved tree conflict. */ 7142 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree, 7143 scratch_pool); 7144 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 7145 } 7146 7147 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 7148 7149 return SVN_NO_ERROR; 7150} 7151 7152/* Implements conflict_option_resolve_func_t. */ 7153static svn_error_t * 7154resolve_merge_incoming_added_file_replace_and_merge( 7155 svn_client_conflict_option_t *option, 7156 svn_client_conflict_t *conflict, 7157 svn_client_ctx_t *ctx, 7158 apr_pool_t *scratch_pool) 7159{ 7160 svn_ra_session_t *ra_session; 7161 const char *url; 7162 const char *corrected_url; 7163 const char *repos_root_url; 7164 const char *incoming_new_repos_relpath; 7165 svn_revnum_t incoming_new_pegrev; 7166 apr_file_t *incoming_new_file; 7167 svn_stream_t *incoming_new_stream; 7168 apr_hash_t *incoming_new_props; 7169 const char *local_abspath; 7170 const char *lock_abspath; 7171 const char *wc_tmpdir; 7172 svn_stream_t *working_file_tmp_stream; 7173 const char *working_file_tmp_abspath; 7174 svn_stream_t *working_file_stream; 7175 apr_hash_t *working_props; 7176 svn_error_t *err; 7177 svn_wc_merge_outcome_t merge_content_outcome; 7178 svn_wc_notify_state_t merge_props_outcome; 7179 apr_file_t *empty_file; 7180 const char *empty_file_abspath; 7181 apr_array_header_t *propdiffs; 7182 7183 local_abspath = svn_client_conflict_get_local_abspath(conflict); 7184 7185 /* Set up tempory storage for the working version of file. */ 7186 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath, 7187 scratch_pool, scratch_pool)); 7188 SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream, 7189 &working_file_tmp_abspath, wc_tmpdir, 7190 svn_io_file_del_on_pool_cleanup, 7191 scratch_pool, scratch_pool)); 7192 7193 /* Copy the detranslated working file to temporary storage. */ 7194 SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx, 7195 local_abspath, local_abspath, 7196 SVN_WC_TRANSLATE_TO_NF, 7197 scratch_pool, scratch_pool)); 7198 SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream, 7199 ctx->cancel_func, ctx->cancel_baton, 7200 scratch_pool)); 7201 7202 /* Get a copy of the working file's properties. */ 7203 SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath, 7204 scratch_pool, scratch_pool)); 7205 7206 /* Fetch the incoming added file from the repository. */ 7207 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 7208 &incoming_new_repos_relpath, &incoming_new_pegrev, 7209 NULL, conflict, scratch_pool, 7210 scratch_pool)); 7211 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 7212 conflict, scratch_pool, 7213 scratch_pool)); 7214 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath, 7215 scratch_pool); 7216 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 7217 url, NULL, NULL, FALSE, FALSE, 7218 ctx, scratch_pool, 7219 scratch_pool)); 7220 if (corrected_url) 7221 url = corrected_url; 7222 SVN_ERR(svn_io_open_unique_file3(&incoming_new_file, NULL, wc_tmpdir, 7223 svn_io_file_del_on_pool_cleanup, 7224 scratch_pool, scratch_pool)); 7225 incoming_new_stream = svn_stream_from_aprfile2(incoming_new_file, TRUE, 7226 scratch_pool); 7227 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, 7228 incoming_new_stream, NULL, /* fetched_rev */ 7229 &incoming_new_props, scratch_pool)); 7230 /* Flush file to disk. */ 7231 SVN_ERR(svn_io_file_flush(incoming_new_file, scratch_pool)); 7232 7233 /* Reset the stream in preparation for adding its content to WC. */ 7234 SVN_ERR(svn_stream_reset(incoming_new_stream)); 7235 7236 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 7237 local_abspath, 7238 scratch_pool, scratch_pool)); 7239 7240 /* ### The following WC modifications should be atomic. */ 7241 7242 /* Replace the working file with the file from the repository. */ 7243 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE, 7244 NULL, NULL, /* don't allow user to cancel here */ 7245 ctx->notify_func2, ctx->notify_baton2, 7246 scratch_pool); 7247 if (err) 7248 goto unlock_wc; 7249 err = svn_wc_add_repos_file4(ctx->wc_ctx, local_abspath, 7250 incoming_new_stream, 7251 NULL, /* ### could we merge first, then set 7252 ### the merged content here? */ 7253 incoming_new_props, 7254 NULL, /* ### merge props first, set here? */ 7255 url, incoming_new_pegrev, 7256 NULL, NULL, /* don't allow user to cancel here */ 7257 scratch_pool); 7258 if (err) 7259 goto unlock_wc; 7260 7261 if (ctx->notify_func2) 7262 { 7263 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, 7264 svn_wc_notify_add, 7265 scratch_pool); 7266 notify->kind = svn_node_file; 7267 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 7268 } 7269 7270 /* Resolve to current working copy state. svn_wc_merge5() requires this. */ 7271 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool); 7272 if (err) 7273 goto unlock_wc; 7274 7275 /* Create an empty file as fake "merge-base" for the two added files. 7276 * The files are not ancestrally related so this is the best we can do. */ 7277 err = svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL, 7278 svn_io_file_del_on_pool_cleanup, 7279 scratch_pool, scratch_pool); 7280 if (err) 7281 goto unlock_wc; 7282 7283 filter_props(incoming_new_props, scratch_pool); 7284 7285 /* Create a property diff for the files. */ 7286 err = svn_prop_diffs(&propdiffs, incoming_new_props, 7287 working_props, scratch_pool); 7288 if (err) 7289 goto unlock_wc; 7290 7291 /* Perform the file merge. */ 7292 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, 7293 ctx->wc_ctx, empty_file_abspath, 7294 working_file_tmp_abspath, local_abspath, 7295 NULL, NULL, NULL, /* labels */ 7296 NULL, NULL, /* conflict versions */ 7297 FALSE, /* dry run */ 7298 NULL, NULL, /* diff3_cmd, merge_options */ 7299 NULL, propdiffs, 7300 NULL, NULL, /* conflict func/baton */ 7301 NULL, NULL, /* don't allow user to cancel here */ 7302 scratch_pool); 7303 if (err) 7304 goto unlock_wc; 7305 7306 if (ctx->notify_func2) 7307 { 7308 svn_wc_notify_t *notify = svn_wc_create_notify( 7309 local_abspath, 7310 svn_wc_notify_update_update, 7311 scratch_pool); 7312 7313 if (merge_content_outcome == svn_wc_merge_conflict) 7314 notify->content_state = svn_wc_notify_state_conflicted; 7315 else 7316 notify->content_state = svn_wc_notify_state_merged; 7317 notify->prop_state = merge_props_outcome; 7318 notify->kind = svn_node_file; 7319 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 7320 } 7321 7322unlock_wc: 7323 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 7324 lock_abspath, 7325 scratch_pool)); 7326 svn_io_sleep_for_timestamps(local_abspath, scratch_pool); 7327 SVN_ERR(err); 7328 7329 SVN_ERR(svn_stream_close(incoming_new_stream)); 7330 7331 if (ctx->notify_func2) 7332 { 7333 svn_wc_notify_t *notify = svn_wc_create_notify( 7334 local_abspath, 7335 svn_wc_notify_resolved_tree, 7336 scratch_pool); 7337 7338 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 7339 } 7340 7341 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 7342 7343 return SVN_NO_ERROR; 7344} 7345 7346static svn_error_t * 7347raise_tree_conflict(const char *local_abspath, 7348 svn_wc_conflict_action_t incoming_change, 7349 svn_wc_conflict_reason_t local_change, 7350 svn_node_kind_t local_node_kind, 7351 svn_node_kind_t merge_left_kind, 7352 svn_node_kind_t merge_right_kind, 7353 const char *repos_root_url, 7354 const char *repos_uuid, 7355 const char *repos_relpath, 7356 svn_revnum_t merge_left_rev, 7357 svn_revnum_t merge_right_rev, 7358 svn_wc_context_t *wc_ctx, 7359 svn_wc_notify_func2_t notify_func2, 7360 void *notify_baton2, 7361 apr_pool_t *scratch_pool) 7362{ 7363 svn_wc_conflict_description2_t *conflict; 7364 const svn_wc_conflict_version_t *left_version; 7365 const svn_wc_conflict_version_t *right_version; 7366 7367 left_version = svn_wc_conflict_version_create2(repos_root_url, 7368 repos_uuid, 7369 repos_relpath, 7370 merge_left_rev, 7371 merge_left_kind, 7372 scratch_pool); 7373 right_version = svn_wc_conflict_version_create2(repos_root_url, 7374 repos_uuid, 7375 repos_relpath, 7376 merge_right_rev, 7377 merge_right_kind, 7378 scratch_pool); 7379 conflict = svn_wc_conflict_description_create_tree2(local_abspath, 7380 local_node_kind, 7381 svn_wc_operation_merge, 7382 left_version, 7383 right_version, 7384 scratch_pool); 7385 conflict->action = incoming_change; 7386 conflict->reason = local_change; 7387 7388 SVN_ERR(svn_wc__add_tree_conflict(wc_ctx, conflict, scratch_pool)); 7389 7390 if (notify_func2) 7391 { 7392 svn_wc_notify_t *notify; 7393 7394 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict, 7395 scratch_pool); 7396 notify->kind = local_node_kind; 7397 notify_func2(notify_baton2, notify, scratch_pool); 7398 } 7399 7400 return SVN_NO_ERROR; 7401} 7402 7403struct merge_newly_added_dir_baton { 7404 const char *target_abspath; 7405 svn_client_ctx_t *ctx; 7406 const char *repos_root_url; 7407 const char *repos_uuid; 7408 const char *added_repos_relpath; 7409 svn_revnum_t merge_left_rev; 7410 svn_revnum_t merge_right_rev; 7411}; 7412 7413static svn_error_t * 7414merge_added_dir_props(const char *target_abspath, 7415 const char *added_repos_relpath, 7416 apr_hash_t *added_props, 7417 const char *repos_root_url, 7418 const char *repos_uuid, 7419 svn_revnum_t merge_left_rev, 7420 svn_revnum_t merge_right_rev, 7421 svn_client_ctx_t *ctx, 7422 apr_pool_t *scratch_pool) 7423{ 7424 svn_wc_notify_state_t property_state; 7425 apr_array_header_t *propchanges; 7426 const svn_wc_conflict_version_t *left_version; 7427 const svn_wc_conflict_version_t *right_version; 7428 apr_hash_index_t *hi; 7429 7430 left_version = svn_wc_conflict_version_create2( 7431 repos_root_url, repos_uuid, added_repos_relpath, 7432 merge_left_rev, svn_node_none, scratch_pool); 7433 7434 right_version = svn_wc_conflict_version_create2( 7435 repos_root_url, repos_uuid, added_repos_relpath, 7436 merge_right_rev, svn_node_dir, scratch_pool); 7437 7438 propchanges = apr_array_make(scratch_pool, apr_hash_count(added_props), 7439 sizeof(svn_prop_t)); 7440 for (hi = apr_hash_first(scratch_pool, added_props); 7441 hi; 7442 hi = apr_hash_next(hi)) 7443 { 7444 svn_prop_t prop; 7445 7446 prop.name = apr_hash_this_key(hi); 7447 prop.value = apr_hash_this_val(hi); 7448 7449 if (svn_wc_is_normal_prop(prop.name)) 7450 APR_ARRAY_PUSH(propchanges, svn_prop_t) = prop; 7451 } 7452 7453 SVN_ERR(svn_wc_merge_props3(&property_state, ctx->wc_ctx, 7454 target_abspath, 7455 left_version, right_version, 7456 apr_hash_make(scratch_pool), 7457 propchanges, 7458 FALSE, /* not a dry-run */ 7459 NULL, NULL, NULL, NULL, 7460 scratch_pool)); 7461 7462 if (ctx->notify_func2) 7463 { 7464 svn_wc_notify_t *notify; 7465 7466 notify = svn_wc_create_notify(target_abspath, 7467 svn_wc_notify_update_update, 7468 scratch_pool); 7469 notify->kind = svn_node_dir; 7470 notify->content_state = svn_wc_notify_state_unchanged;; 7471 notify->prop_state = property_state; 7472 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 7473 } 7474 7475 return SVN_NO_ERROR; 7476} 7477 7478/* An svn_diff_tree_processor_t callback. */ 7479static svn_error_t * 7480diff_dir_added(const char *relpath, 7481 const svn_diff_source_t *copyfrom_source, 7482 const svn_diff_source_t *right_source, 7483 apr_hash_t *copyfrom_props, 7484 apr_hash_t *right_props, 7485 void *dir_baton, 7486 const struct svn_diff_tree_processor_t *processor, 7487 apr_pool_t *scratch_pool) 7488{ 7489 struct merge_newly_added_dir_baton *b = processor->baton; 7490 const char *local_abspath; 7491 const char *copyfrom_url; 7492 svn_node_kind_t db_kind; 7493 svn_node_kind_t on_disk_kind; 7494 apr_hash_index_t *hi; 7495 7496 /* Handle the root of the added directory tree. */ 7497 if (relpath[0] == '\0') 7498 { 7499 /* ### svn_wc_merge_props3() requires this... */ 7500 SVN_ERR(svn_wc__del_tree_conflict(b->ctx->wc_ctx, b->target_abspath, 7501 scratch_pool)); 7502 SVN_ERR(merge_added_dir_props(b->target_abspath, 7503 b->added_repos_relpath, right_props, 7504 b->repos_root_url, b->repos_uuid, 7505 b->merge_left_rev, b->merge_right_rev, 7506 b->ctx, scratch_pool)); 7507 return SVN_NO_ERROR; 7508 7509 } 7510 7511 local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool); 7512 7513 SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath, 7514 FALSE, FALSE, scratch_pool)); 7515 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool)); 7516 7517 if (db_kind == svn_node_dir && on_disk_kind == svn_node_dir) 7518 { 7519 SVN_ERR(merge_added_dir_props(svn_dirent_join(b->target_abspath, relpath, 7520 scratch_pool), 7521 b->added_repos_relpath, right_props, 7522 b->repos_root_url, b->repos_uuid, 7523 b->merge_left_rev, b->merge_right_rev, 7524 b->ctx, scratch_pool)); 7525 return SVN_NO_ERROR; 7526 } 7527 7528 if (db_kind != svn_node_none && db_kind != svn_node_unknown) 7529 { 7530 SVN_ERR(raise_tree_conflict( 7531 local_abspath, svn_wc_conflict_action_add, 7532 svn_wc_conflict_reason_obstructed, 7533 db_kind, svn_node_none, svn_node_dir, 7534 b->repos_root_url, b->repos_uuid, 7535 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool), 7536 b->merge_left_rev, b->merge_right_rev, 7537 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2, 7538 scratch_pool)); 7539 return SVN_NO_ERROR; 7540 } 7541 7542 if (on_disk_kind != svn_node_none) 7543 { 7544 SVN_ERR(raise_tree_conflict( 7545 local_abspath, svn_wc_conflict_action_add, 7546 svn_wc_conflict_reason_obstructed, db_kind, 7547 svn_node_none, svn_node_dir, b->repos_root_url, b->repos_uuid, 7548 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool), 7549 b->merge_left_rev, b->merge_right_rev, 7550 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2, 7551 scratch_pool)); 7552 return SVN_NO_ERROR; 7553 } 7554 7555 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); 7556 copyfrom_url = apr_pstrcat(scratch_pool, b->repos_root_url, "/", 7557 right_source->repos_relpath, SVN_VA_NULL); 7558 SVN_ERR(svn_wc_add4(b->ctx->wc_ctx, local_abspath, svn_depth_infinity, 7559 copyfrom_url, right_source->revision, 7560 NULL, NULL, /* cancel func/baton */ 7561 b->ctx->notify_func2, b->ctx->notify_baton2, 7562 scratch_pool)); 7563 7564 for (hi = apr_hash_first(scratch_pool, right_props); 7565 hi; 7566 hi = apr_hash_next(hi)) 7567 { 7568 const char *propname = apr_hash_this_key(hi); 7569 const svn_string_t *propval = apr_hash_this_val(hi); 7570 7571 SVN_ERR(svn_wc_prop_set4(b->ctx->wc_ctx, local_abspath, 7572 propname, propval, svn_depth_empty, 7573 FALSE, NULL /* do not skip checks */, 7574 NULL, NULL, /* cancel func/baton */ 7575 b->ctx->notify_func2, b->ctx->notify_baton2, 7576 scratch_pool)); 7577 } 7578 7579 return SVN_NO_ERROR; 7580} 7581 7582static svn_error_t * 7583merge_added_files(const char *local_abspath, 7584 const char *incoming_added_file_abspath, 7585 apr_hash_t *incoming_added_file_props, 7586 svn_client_ctx_t *ctx, 7587 apr_pool_t *scratch_pool) 7588{ 7589 svn_wc_merge_outcome_t merge_content_outcome; 7590 svn_wc_notify_state_t merge_props_outcome; 7591 apr_file_t *empty_file; 7592 const char *empty_file_abspath; 7593 apr_array_header_t *propdiffs; 7594 apr_hash_t *working_props; 7595 7596 /* Create an empty file as fake "merge-base" for the two added files. 7597 * The files are not ancestrally related so this is the best we can do. */ 7598 SVN_ERR(svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL, 7599 svn_io_file_del_on_pool_cleanup, 7600 scratch_pool, scratch_pool)); 7601 7602 /* Get a copy of the working file's properties. */ 7603 SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath, 7604 scratch_pool, scratch_pool)); 7605 7606 /* Create a property diff for the files. */ 7607 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_added_file_props, 7608 working_props, scratch_pool)); 7609 7610 /* Perform the file merge. */ 7611 SVN_ERR(svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, 7612 ctx->wc_ctx, empty_file_abspath, 7613 incoming_added_file_abspath, local_abspath, 7614 NULL, NULL, NULL, /* labels */ 7615 NULL, NULL, /* conflict versions */ 7616 FALSE, /* dry run */ 7617 NULL, NULL, /* diff3_cmd, merge_options */ 7618 NULL, propdiffs, 7619 NULL, NULL, /* conflict func/baton */ 7620 NULL, NULL, /* don't allow user to cancel here */ 7621 scratch_pool)); 7622 7623 if (ctx->notify_func2) 7624 { 7625 svn_wc_notify_t *notify = svn_wc_create_notify( 7626 local_abspath, 7627 svn_wc_notify_update_update, 7628 scratch_pool); 7629 7630 if (merge_content_outcome == svn_wc_merge_conflict) 7631 notify->content_state = svn_wc_notify_state_conflicted; 7632 else 7633 notify->content_state = svn_wc_notify_state_merged; 7634 notify->prop_state = merge_props_outcome; 7635 notify->kind = svn_node_file; 7636 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 7637 } 7638 7639 return SVN_NO_ERROR; 7640} 7641 7642/* An svn_diff_tree_processor_t callback. */ 7643static svn_error_t * 7644diff_file_added(const char *relpath, 7645 const svn_diff_source_t *copyfrom_source, 7646 const svn_diff_source_t *right_source, 7647 const char *copyfrom_file, 7648 const char *right_file, 7649 apr_hash_t *copyfrom_props, 7650 apr_hash_t *right_props, 7651 void *file_baton, 7652 const struct svn_diff_tree_processor_t *processor, 7653 apr_pool_t *scratch_pool) 7654{ 7655 struct merge_newly_added_dir_baton *b = processor->baton; 7656 const char *local_abspath; 7657 svn_node_kind_t db_kind; 7658 svn_node_kind_t on_disk_kind; 7659 apr_array_header_t *propsarray; 7660 apr_array_header_t *regular_props; 7661 7662 local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool); 7663 7664 SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath, 7665 FALSE, FALSE, scratch_pool)); 7666 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool)); 7667 7668 if (db_kind == svn_node_file && on_disk_kind == svn_node_file) 7669 { 7670 propsarray = svn_prop_hash_to_array(right_props, scratch_pool); 7671 SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, ®ular_props, 7672 scratch_pool)); 7673 SVN_ERR(merge_added_files(local_abspath, right_file, 7674 svn_prop_array_to_hash(regular_props, 7675 scratch_pool), 7676 b->ctx, scratch_pool)); 7677 return SVN_NO_ERROR; 7678 } 7679 7680 if (db_kind != svn_node_none && db_kind != svn_node_unknown) 7681 { 7682 SVN_ERR(raise_tree_conflict( 7683 local_abspath, svn_wc_conflict_action_add, 7684 svn_wc_conflict_reason_obstructed, 7685 db_kind, svn_node_none, svn_node_file, 7686 b->repos_root_url, b->repos_uuid, 7687 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool), 7688 b->merge_left_rev, b->merge_right_rev, 7689 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2, 7690 scratch_pool)); 7691 return SVN_NO_ERROR; 7692 } 7693 7694 if (on_disk_kind != svn_node_none) 7695 { 7696 SVN_ERR(raise_tree_conflict( 7697 local_abspath, svn_wc_conflict_action_add, 7698 svn_wc_conflict_reason_obstructed, db_kind, 7699 svn_node_none, svn_node_file, b->repos_root_url, b->repos_uuid, 7700 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool), 7701 b->merge_left_rev, b->merge_right_rev, 7702 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2, 7703 scratch_pool)); 7704 return SVN_NO_ERROR; 7705 } 7706 7707 propsarray = svn_prop_hash_to_array(right_props, scratch_pool); 7708 SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, ®ular_props, 7709 scratch_pool)); 7710 SVN_ERR(svn_io_copy_file(right_file, local_abspath, FALSE, scratch_pool)); 7711 SVN_ERR(svn_wc_add_from_disk3(b->ctx->wc_ctx, local_abspath, 7712 svn_prop_array_to_hash(regular_props, 7713 scratch_pool), 7714 FALSE, b->ctx->notify_func2, 7715 b->ctx->notify_baton2, scratch_pool)); 7716 7717 return SVN_NO_ERROR; 7718} 7719 7720/* Merge a newly added directory into TARGET_ABSPATH in the working copy. 7721 * 7722 * This uses a diff-tree processor because our standard merge operation 7723 * is not set up for merges where the merge-source anchor is itself an 7724 * added directory (i.e. does not exist on one side of the diff). 7725 * The standard merge will only merge additions of children of a path 7726 * that exists across the entire revision range being merged. 7727 * But in our case, SOURCE1 does not yet exist in REV1, but SOURCE2 7728 * does exist in REV2. Thus we use a diff processor. 7729 */ 7730static svn_error_t * 7731merge_newly_added_dir(const char *added_repos_relpath, 7732 const char *source1, 7733 svn_revnum_t rev1, 7734 const char *source2, 7735 svn_revnum_t rev2, 7736 const char *target_abspath, 7737 svn_boolean_t reverse_merge, 7738 svn_client_ctx_t *ctx, 7739 apr_pool_t *result_pool, 7740 apr_pool_t *scratch_pool) 7741{ 7742 svn_diff_tree_processor_t *processor; 7743 struct merge_newly_added_dir_baton baton = { 0 }; 7744 const svn_diff_tree_processor_t *diff_processor; 7745 svn_ra_session_t *ra_session; 7746 const char *corrected_url; 7747 svn_ra_session_t *extra_ra_session; 7748 const svn_ra_reporter3_t *reporter; 7749 void *reporter_baton; 7750 const svn_delta_editor_t *diff_editor; 7751 void *diff_edit_baton; 7752 const char *anchor1; 7753 const char *anchor2; 7754 const char *target1; 7755 const char *target2; 7756 7757 svn_uri_split(&anchor1, &target1, source1, scratch_pool); 7758 svn_uri_split(&anchor2, &target2, source2, scratch_pool); 7759 7760 baton.target_abspath = target_abspath; 7761 baton.ctx = ctx; 7762 baton.added_repos_relpath = added_repos_relpath; 7763 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, 7764 &baton.repos_root_url, &baton.repos_uuid, 7765 ctx->wc_ctx, target_abspath, 7766 scratch_pool, scratch_pool)); 7767 baton.merge_left_rev = rev1; 7768 baton.merge_right_rev = rev2; 7769 7770 processor = svn_diff__tree_processor_create(&baton, scratch_pool); 7771 processor->dir_added = diff_dir_added; 7772 processor->file_added = diff_file_added; 7773 7774 diff_processor = processor; 7775 if (reverse_merge) 7776 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, 7777 scratch_pool); 7778 7779 /* Filter the first path component using a filter processor, until we fixed 7780 the diff processing to handle this directly */ 7781 diff_processor = svn_diff__tree_processor_filter_create( 7782 diff_processor, target1, scratch_pool); 7783 7784 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 7785 anchor2, NULL, NULL, FALSE, 7786 FALSE, ctx, 7787 scratch_pool, scratch_pool)); 7788 if (corrected_url) 7789 anchor2 = corrected_url; 7790 7791 /* Extra RA session is used during the editor calls to fetch file contents. */ 7792 SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor2, 7793 scratch_pool, scratch_pool)); 7794 7795 /* Create a repos-repos diff editor. */ 7796 SVN_ERR(svn_client__get_diff_editor2( 7797 &diff_editor, &diff_edit_baton, 7798 extra_ra_session, svn_depth_infinity, rev1, TRUE, 7799 diff_processor, ctx->cancel_func, ctx->cancel_baton, 7800 scratch_pool)); 7801 7802 /* We want to switch our txn into URL2 */ 7803 SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton, 7804 rev2, target1, svn_depth_infinity, TRUE, TRUE, 7805 source2, diff_editor, diff_edit_baton, scratch_pool)); 7806 7807 /* Drive the reporter; do the diff. */ 7808 SVN_ERR(reporter->set_path(reporter_baton, "", rev1, 7809 svn_depth_infinity, 7810 FALSE, NULL, 7811 scratch_pool)); 7812 7813 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool)); 7814 7815 return SVN_NO_ERROR; 7816} 7817 7818/* Implements conflict_option_resolve_func_t. */ 7819static svn_error_t * 7820resolve_merge_incoming_added_dir_merge(svn_client_conflict_option_t *option, 7821 svn_client_conflict_t *conflict, 7822 svn_client_ctx_t *ctx, 7823 apr_pool_t *scratch_pool) 7824{ 7825 const char *repos_root_url; 7826 const char *incoming_old_repos_relpath; 7827 svn_revnum_t incoming_old_pegrev; 7828 const char *incoming_new_repos_relpath; 7829 svn_revnum_t incoming_new_pegrev; 7830 const char *local_abspath; 7831 const char *lock_abspath; 7832 struct conflict_tree_incoming_add_details *details; 7833 const char *added_repos_relpath; 7834 const char *source1; 7835 svn_revnum_t rev1; 7836 const char *source2; 7837 svn_revnum_t rev2; 7838 svn_error_t *err; 7839 7840 local_abspath = svn_client_conflict_get_local_abspath(conflict); 7841 7842 details = conflict->tree_conflict_incoming_details; 7843 if (details == NULL) 7844 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 7845 _("Conflict resolution option '%d' requires " 7846 "details for tree conflict at '%s' to be " 7847 "fetched from the repository"), 7848 option->id, 7849 svn_dirent_local_style(local_abspath, 7850 scratch_pool)); 7851 7852 /* Set up merge sources to merge the entire incoming added directory tree. */ 7853 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 7854 conflict, scratch_pool, 7855 scratch_pool)); 7856 source1 = svn_path_url_add_component2(repos_root_url, 7857 details->repos_relpath, 7858 scratch_pool); 7859 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 7860 &incoming_old_repos_relpath, &incoming_old_pegrev, 7861 NULL, conflict, scratch_pool, scratch_pool)); 7862 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 7863 &incoming_new_repos_relpath, &incoming_new_pegrev, 7864 NULL, conflict, scratch_pool, scratch_pool)); 7865 if (incoming_old_pegrev < incoming_new_pegrev) /* forward merge */ 7866 { 7867 if (details->added_rev == SVN_INVALID_REVNUM) 7868 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 7869 _("Could not determine when '%s' was " 7870 "added the repository"), 7871 svn_dirent_local_style(local_abspath, 7872 scratch_pool)); 7873 rev1 = rev_below(details->added_rev); 7874 source2 = svn_path_url_add_component2(repos_root_url, 7875 incoming_new_repos_relpath, 7876 scratch_pool); 7877 rev2 = incoming_new_pegrev; 7878 added_repos_relpath = incoming_new_repos_relpath; 7879 } 7880 else /* reverse-merge */ 7881 { 7882 if (details->deleted_rev == SVN_INVALID_REVNUM) 7883 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 7884 _("Could not determine when '%s' was " 7885 "deleted from the repository"), 7886 svn_dirent_local_style(local_abspath, 7887 scratch_pool)); 7888 rev1 = details->deleted_rev; 7889 source2 = svn_path_url_add_component2(repos_root_url, 7890 incoming_old_repos_relpath, 7891 scratch_pool); 7892 rev2 = incoming_old_pegrev; 7893 added_repos_relpath = incoming_new_repos_relpath; 7894 } 7895 7896 /* ### The following WC modifications should be atomic. */ 7897 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 7898 local_abspath, 7899 scratch_pool, scratch_pool)); 7900 7901 /* ### wrap in a transaction */ 7902 err = merge_newly_added_dir(added_repos_relpath, 7903 source1, rev1, source2, rev2, 7904 local_abspath, 7905 (incoming_old_pegrev > incoming_new_pegrev), 7906 ctx, scratch_pool, scratch_pool); 7907 if (!err) 7908 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool); 7909 7910 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 7911 lock_abspath, 7912 scratch_pool)); 7913 svn_io_sleep_for_timestamps(local_abspath, scratch_pool); 7914 SVN_ERR(err); 7915 7916 if (ctx->notify_func2) 7917 ctx->notify_func2(ctx->notify_baton2, 7918 svn_wc_create_notify(local_abspath, 7919 svn_wc_notify_resolved_tree, 7920 scratch_pool), 7921 scratch_pool); 7922 7923 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 7924 7925 return SVN_NO_ERROR; 7926} 7927 7928/* Implements conflict_option_resolve_func_t. */ 7929static svn_error_t * 7930resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option, 7931 svn_client_conflict_t *conflict, 7932 svn_client_ctx_t *ctx, 7933 apr_pool_t *scratch_pool) 7934{ 7935 const char *local_abspath; 7936 const char *lock_abspath; 7937 svn_error_t *err; 7938 svn_wc_conflict_reason_t local_change; 7939 7940 local_abspath = svn_client_conflict_get_local_abspath(conflict); 7941 local_change = svn_client_conflict_get_local_change(conflict); 7942 7943 if (local_change == svn_wc_conflict_reason_unversioned) 7944 { 7945 char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 7946 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 7947 &lock_abspath, ctx->wc_ctx, parent_abspath, 7948 scratch_pool, scratch_pool)); 7949 7950 /* The update/switch operation has added the incoming versioned 7951 * directory as a deleted op-depth layer. We can revert this layer 7952 * to make the incoming tree appear in the working copy. 7953 * This meta-data-only revert operation effecively merges the 7954 * versioned and unversioned trees but leaves all unversioned files as 7955 * they were. This is the best we can do; 3-way merging of unversioned 7956 * files with files from the repository is impossible because there is 7957 * no known merge base. No unversioned data will be lost, and any 7958 * differences to files in the repository will show up in 'svn diff'. */ 7959 err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_infinity, 7960 FALSE, NULL, TRUE, TRUE /* metadata_only */, 7961 TRUE /*added_keep_local*/, 7962 NULL, NULL, /* no cancellation */ 7963 ctx->notify_func2, ctx->notify_baton2, 7964 scratch_pool); 7965 } 7966 else 7967 { 7968 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 7969 &lock_abspath, ctx->wc_ctx, local_abspath, 7970 scratch_pool, scratch_pool)); 7971 err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx, 7972 local_abspath, 7973 ctx->cancel_func, 7974 ctx->cancel_baton, 7975 ctx->notify_func2, 7976 ctx->notify_baton2, 7977 scratch_pool); 7978 } 7979 7980 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 7981 lock_abspath, 7982 scratch_pool)); 7983 SVN_ERR(err); 7984 7985 return SVN_NO_ERROR; 7986} 7987 7988/* Resolve a dir/dir "incoming add vs local obstruction" tree conflict by 7989 * replacing the local directory with the incoming directory. 7990 * If MERGE_DIRS is set, also merge the directories after replacing. */ 7991static svn_error_t * 7992merge_incoming_added_dir_replace(svn_client_conflict_option_t *option, 7993 svn_client_conflict_t *conflict, 7994 svn_client_ctx_t *ctx, 7995 svn_boolean_t merge_dirs, 7996 apr_pool_t *scratch_pool) 7997{ 7998 svn_ra_session_t *ra_session; 7999 const char *url; 8000 const char *corrected_url; 8001 const char *repos_root_url; 8002 const char *incoming_new_repos_relpath; 8003 svn_revnum_t incoming_new_pegrev; 8004 const char *local_abspath; 8005 const char *lock_abspath; 8006 svn_error_t *err; 8007 svn_boolean_t timestamp_sleep; 8008 8009 local_abspath = svn_client_conflict_get_local_abspath(conflict); 8010 8011 /* Find the URL of the incoming added directory in the repository. */ 8012 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 8013 &incoming_new_repos_relpath, &incoming_new_pegrev, 8014 NULL, conflict, scratch_pool, 8015 scratch_pool)); 8016 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 8017 conflict, scratch_pool, 8018 scratch_pool)); 8019 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath, 8020 scratch_pool); 8021 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 8022 url, NULL, NULL, FALSE, FALSE, 8023 ctx, scratch_pool, 8024 scratch_pool)); 8025 if (corrected_url) 8026 url = corrected_url; 8027 8028 /* ### The following WC modifications should be atomic. */ 8029 8030 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 8031 svn_dirent_dirname( 8032 local_abspath, 8033 scratch_pool), 8034 scratch_pool, scratch_pool)); 8035 8036 /* Remove the working directory. */ 8037 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE, 8038 NULL, NULL, /* don't allow user to cancel here */ 8039 ctx->notify_func2, ctx->notify_baton2, 8040 scratch_pool); 8041 if (err) 8042 goto unlock_wc; 8043 8044 err = svn_client__repos_to_wc_copy_by_editor(×tamp_sleep, 8045 svn_node_dir, 8046 url, incoming_new_pegrev, 8047 local_abspath, 8048 ra_session, ctx, scratch_pool); 8049 if (err) 8050 goto unlock_wc; 8051 8052 if (ctx->notify_func2) 8053 { 8054 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, 8055 svn_wc_notify_add, 8056 scratch_pool); 8057 notify->kind = svn_node_dir; 8058 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 8059 } 8060 8061 /* Resolve to current working copy state. 8062 * svn_client__merge_locked() requires this. */ 8063 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool); 8064 if (err) 8065 goto unlock_wc; 8066 8067 if (merge_dirs) 8068 { 8069 svn_revnum_t base_revision; 8070 const char *base_repos_relpath; 8071 struct find_added_rev_baton b = { 0 }; 8072 8073 /* Find the URL and revision of the directory we have just replaced. */ 8074 err = svn_wc__node_get_base(NULL, &base_revision, &base_repos_relpath, 8075 NULL, NULL, NULL, ctx->wc_ctx, local_abspath, 8076 FALSE, scratch_pool, scratch_pool); 8077 if (err) 8078 goto unlock_wc; 8079 8080 url = svn_path_url_add_component2(repos_root_url, base_repos_relpath, 8081 scratch_pool); 8082 8083 /* Trace the replaced directory's history to its origin. */ 8084 err = svn_ra_reparent(ra_session, url, scratch_pool); 8085 if (err) 8086 goto unlock_wc; 8087 b.victim_abspath = local_abspath; 8088 b.ctx = ctx; 8089 b.added_rev = SVN_INVALID_REVNUM; 8090 b.repos_relpath = NULL; 8091 b.parent_repos_relpath = svn_relpath_dirname(base_repos_relpath, 8092 scratch_pool); 8093 b.pool = scratch_pool; 8094 8095 err = svn_ra_get_location_segments(ra_session, "", base_revision, 8096 base_revision, SVN_INVALID_REVNUM, 8097 find_added_rev, &b, 8098 scratch_pool); 8099 if (err) 8100 goto unlock_wc; 8101 8102 if (b.added_rev == SVN_INVALID_REVNUM) 8103 { 8104 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8105 _("Could not determine the revision in " 8106 "which '^/%s' was added to the " 8107 "repository.\n"), 8108 base_repos_relpath); 8109 goto unlock_wc; 8110 } 8111 8112 /* Merge the replaced directory into the directory which replaced it. 8113 * We do not need to consider a reverse-merge here since the source of 8114 * this merge was part of the merge target working copy, not a branch 8115 * in the repository. */ 8116 err = merge_newly_added_dir(base_repos_relpath, 8117 url, rev_below(b.added_rev), url, 8118 base_revision, local_abspath, FALSE, 8119 ctx, scratch_pool, scratch_pool); 8120 if (err) 8121 goto unlock_wc; 8122 } 8123 8124unlock_wc: 8125 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 8126 lock_abspath, 8127 scratch_pool)); 8128 svn_io_sleep_for_timestamps(local_abspath, scratch_pool); 8129 SVN_ERR(err); 8130 8131 if (ctx->notify_func2) 8132 { 8133 svn_wc_notify_t *notify = svn_wc_create_notify( 8134 local_abspath, 8135 svn_wc_notify_resolved_tree, 8136 scratch_pool); 8137 8138 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 8139 } 8140 8141 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 8142 8143 return SVN_NO_ERROR; 8144} 8145 8146/* Implements conflict_option_resolve_func_t. */ 8147static svn_error_t * 8148resolve_merge_incoming_added_dir_replace(svn_client_conflict_option_t *option, 8149 svn_client_conflict_t *conflict, 8150 svn_client_ctx_t *ctx, 8151 apr_pool_t *scratch_pool) 8152{ 8153 return svn_error_trace(merge_incoming_added_dir_replace(option, 8154 conflict, 8155 ctx, 8156 FALSE, 8157 scratch_pool)); 8158} 8159 8160/* Implements conflict_option_resolve_func_t. */ 8161static svn_error_t * 8162resolve_merge_incoming_added_dir_replace_and_merge( 8163 svn_client_conflict_option_t *option, 8164 svn_client_conflict_t *conflict, 8165 svn_client_ctx_t *ctx, 8166 apr_pool_t *scratch_pool) 8167{ 8168 return svn_error_trace(merge_incoming_added_dir_replace(option, 8169 conflict, 8170 ctx, 8171 TRUE, 8172 scratch_pool)); 8173} 8174 8175/* Ensure the conflict victim is a copy of itself from before it was deleted. 8176 * Update and switch are supposed to set this up when flagging the conflict. */ 8177static svn_error_t * 8178ensure_local_edit_vs_incoming_deletion_copied_state( 8179 struct conflict_tree_incoming_delete_details *details, 8180 svn_wc_operation_t operation, 8181 const char *wcroot_abspath, 8182 svn_client_conflict_t *conflict, 8183 svn_client_ctx_t *ctx, 8184 apr_pool_t *scratch_pool) 8185{ 8186 8187 svn_boolean_t is_copy; 8188 svn_revnum_t copyfrom_rev; 8189 const char *copyfrom_repos_relpath; 8190 8191 SVN_ERR_ASSERT(operation == svn_wc_operation_update || 8192 operation == svn_wc_operation_switch); 8193 8194 SVN_ERR(svn_wc__node_get_origin(&is_copy, ©from_rev, 8195 ©from_repos_relpath, 8196 NULL, NULL, NULL, NULL, 8197 ctx->wc_ctx, conflict->local_abspath, 8198 FALSE, scratch_pool, scratch_pool)); 8199 if (!is_copy) 8200 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8201 _("Cannot resolve tree conflict on '%s' " 8202 "(expected a copied item, but the item " 8203 "is not a copy)"), 8204 svn_dirent_local_style( 8205 svn_dirent_skip_ancestor( 8206 wcroot_abspath, 8207 conflict->local_abspath), 8208 scratch_pool)); 8209 else if (details->deleted_rev != SVN_INVALID_REVNUM && 8210 copyfrom_rev >= details->deleted_rev) 8211 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8212 _("Cannot resolve tree conflict on '%s' " 8213 "(expected an item copied from a revision " 8214 "smaller than r%ld, but the item was " 8215 "copied from r%ld)"), 8216 svn_dirent_local_style( 8217 svn_dirent_skip_ancestor( 8218 wcroot_abspath, conflict->local_abspath), 8219 scratch_pool), 8220 details->deleted_rev, copyfrom_rev); 8221 else if (details->added_rev != SVN_INVALID_REVNUM && 8222 copyfrom_rev < details->added_rev) 8223 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8224 _("Cannot resolve tree conflict on '%s' " 8225 "(expected an item copied from a revision " 8226 "larger than r%ld, but the item was " 8227 "copied from r%ld)"), 8228 svn_dirent_local_style( 8229 svn_dirent_skip_ancestor( 8230 wcroot_abspath, conflict->local_abspath), 8231 scratch_pool), 8232 details->added_rev, copyfrom_rev); 8233 else if (operation == svn_wc_operation_update) 8234 { 8235 const char *old_repos_relpath; 8236 8237 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 8238 &old_repos_relpath, NULL, NULL, conflict, 8239 scratch_pool, scratch_pool)); 8240 if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 && 8241 strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0) 8242 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8243 _("Cannot resolve tree conflict on '%s' " 8244 "(expected an item copied from '^/%s' " 8245 "or from '^/%s' but the item was " 8246 "copied from '^/%s@%ld')"), 8247 svn_dirent_local_style( 8248 svn_dirent_skip_ancestor( 8249 wcroot_abspath, conflict->local_abspath), 8250 scratch_pool), 8251 details->repos_relpath, 8252 old_repos_relpath, 8253 copyfrom_repos_relpath, copyfrom_rev); 8254 } 8255 else if (operation == svn_wc_operation_switch) 8256 { 8257 const char *old_repos_relpath; 8258 8259 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 8260 &old_repos_relpath, NULL, NULL, conflict, 8261 scratch_pool, scratch_pool)); 8262 8263 if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0) 8264 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8265 _("Cannot resolve tree conflict on '%s' " 8266 "(expected an item copied from '^/%s', " 8267 "but the item was copied from " 8268 "'^/%s@%ld')"), 8269 svn_dirent_local_style( 8270 svn_dirent_skip_ancestor( 8271 wcroot_abspath, 8272 conflict->local_abspath), 8273 scratch_pool), 8274 old_repos_relpath, 8275 copyfrom_repos_relpath, copyfrom_rev); 8276 } 8277 8278 return SVN_NO_ERROR; 8279} 8280 8281/* Verify the local working copy state matches what we expect when an 8282 * incoming deletion tree conflict exists. 8283 * We assume update/merge/switch operations leave the working copy in a 8284 * state which prefers the local change and cancels the deletion. 8285 * Run a quick sanity check and error out if it looks as if the 8286 * working copy was modified since, even though it's not easy to make 8287 * such modifications without also clearing the conflict marker. */ 8288static svn_error_t * 8289verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict, 8290 svn_client_conflict_option_t *option, 8291 svn_client_ctx_t *ctx, 8292 apr_pool_t *scratch_pool) 8293{ 8294 const char *local_abspath; 8295 const char *wcroot_abspath; 8296 svn_wc_operation_t operation; 8297 svn_wc_conflict_reason_t local_change; 8298 8299 local_abspath = svn_client_conflict_get_local_abspath(conflict); 8300 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 8301 local_abspath, scratch_pool, 8302 scratch_pool)); 8303 operation = svn_client_conflict_get_operation(conflict); 8304 local_change = svn_client_conflict_get_local_change(conflict); 8305 8306 if (operation == svn_wc_operation_update || 8307 operation == svn_wc_operation_switch) 8308 { 8309 struct conflict_tree_incoming_delete_details *details; 8310 8311 details = conflict->tree_conflict_incoming_details; 8312 if (details == NULL) 8313 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8314 _("Conflict resolution option '%d' requires " 8315 "details for tree conflict at '%s' to be " 8316 "fetched from the repository."), 8317 option->id, 8318 svn_dirent_local_style(local_abspath, 8319 scratch_pool)); 8320 8321 if (details->deleted_rev == SVN_INVALID_REVNUM && 8322 details->added_rev == SVN_INVALID_REVNUM) 8323 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8324 _("Could not find the revision in which '%s' " 8325 "was deleted from the repository"), 8326 svn_dirent_local_style( 8327 svn_dirent_skip_ancestor( 8328 wcroot_abspath, 8329 conflict->local_abspath), 8330 scratch_pool)); 8331 8332 if (local_change == svn_wc_conflict_reason_edited) 8333 SVN_ERR(ensure_local_edit_vs_incoming_deletion_copied_state( 8334 details, operation, wcroot_abspath, conflict, ctx, 8335 scratch_pool)); 8336 } 8337 else if (operation == svn_wc_operation_merge) 8338 { 8339 svn_node_kind_t victim_node_kind; 8340 svn_node_kind_t on_disk_kind; 8341 8342 /* For merge, all we can do is ensure that the item still exists. */ 8343 victim_node_kind = 8344 svn_client_conflict_tree_get_victim_node_kind(conflict); 8345 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool)); 8346 8347 if (victim_node_kind != on_disk_kind) 8348 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8349 _("Cannot resolve tree conflict on '%s' " 8350 "(expected node kind '%s' but found '%s')"), 8351 svn_dirent_local_style( 8352 svn_dirent_skip_ancestor( 8353 wcroot_abspath, conflict->local_abspath), 8354 scratch_pool), 8355 svn_node_kind_to_word(victim_node_kind), 8356 svn_node_kind_to_word(on_disk_kind)); 8357 } 8358 8359 return SVN_NO_ERROR; 8360} 8361 8362/* Implements conflict_option_resolve_func_t. */ 8363static svn_error_t * 8364resolve_incoming_delete_ignore(svn_client_conflict_option_t *option, 8365 svn_client_conflict_t *conflict, 8366 svn_client_ctx_t *ctx, 8367 apr_pool_t *scratch_pool) 8368{ 8369 svn_client_conflict_option_id_t option_id; 8370 const char *local_abspath; 8371 const char *lock_abspath; 8372 svn_error_t *err; 8373 8374 option_id = svn_client_conflict_option_get_id(option); 8375 local_abspath = svn_client_conflict_get_local_abspath(conflict); 8376 8377 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 8378 local_abspath, 8379 scratch_pool, scratch_pool)); 8380 8381 err = verify_local_state_for_incoming_delete(conflict, option, ctx, 8382 scratch_pool); 8383 if (err) 8384 goto unlock_wc; 8385 8386 /* Resolve to the current working copy state. */ 8387 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool); 8388 8389 /* svn_wc__del_tree_conflict doesn't handle notification for us */ 8390 if (ctx->notify_func2) 8391 ctx->notify_func2(ctx->notify_baton2, 8392 svn_wc_create_notify(local_abspath, 8393 svn_wc_notify_resolved_tree, 8394 scratch_pool), 8395 scratch_pool); 8396 8397unlock_wc: 8398 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 8399 lock_abspath, 8400 scratch_pool)); 8401 SVN_ERR(err); 8402 8403 conflict->resolution_tree = option_id; 8404 8405 return SVN_NO_ERROR; 8406} 8407 8408/* Implements conflict_option_resolve_func_t. */ 8409static svn_error_t * 8410resolve_incoming_delete_accept(svn_client_conflict_option_t *option, 8411 svn_client_conflict_t *conflict, 8412 svn_client_ctx_t *ctx, 8413 apr_pool_t *scratch_pool) 8414{ 8415 svn_client_conflict_option_id_t option_id; 8416 const char *local_abspath; 8417 const char *parent_abspath; 8418 const char *lock_abspath; 8419 svn_error_t *err; 8420 8421 option_id = svn_client_conflict_option_get_id(option); 8422 local_abspath = svn_client_conflict_get_local_abspath(conflict); 8423 8424 /* Deleting a node requires a lock on the node's parent. */ 8425 parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 8426 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, 8427 parent_abspath, 8428 scratch_pool, scratch_pool)); 8429 8430 err = verify_local_state_for_incoming_delete(conflict, option, ctx, 8431 scratch_pool); 8432 if (err) 8433 goto unlock_wc; 8434 8435 /* Delete the tree conflict victim. Marks the conflict resolved. */ 8436 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE, 8437 NULL, NULL, /* don't allow user to cancel here */ 8438 ctx->notify_func2, ctx->notify_baton2, 8439 scratch_pool); 8440 if (err) 8441 { 8442 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 8443 { 8444 /* Not a versioned path. This can happen if the victim has already 8445 * been deleted in our branche's history, for example. Either way, 8446 * the item is gone, which is what we want, so don't treat this as 8447 * a fatal error. */ 8448 svn_error_clear(err); 8449 8450 /* Resolve to current working copy state. */ 8451 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, 8452 scratch_pool); 8453 } 8454 8455 if (err) 8456 goto unlock_wc; 8457 } 8458 8459 if (ctx->notify_func2) 8460 ctx->notify_func2(ctx->notify_baton2, 8461 svn_wc_create_notify(local_abspath, 8462 svn_wc_notify_resolved_tree, 8463 scratch_pool), 8464 scratch_pool); 8465 8466unlock_wc: 8467 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 8468 lock_abspath, 8469 scratch_pool)); 8470 SVN_ERR(err); 8471 8472 conflict->resolution_tree = option_id; 8473 8474 return SVN_NO_ERROR; 8475} 8476 8477/* Implements conflict_option_resolve_func_t. */ 8478static svn_error_t * 8479resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, 8480 svn_client_conflict_t *conflict, 8481 svn_client_ctx_t *ctx, 8482 apr_pool_t *scratch_pool) 8483{ 8484 svn_client_conflict_option_id_t option_id; 8485 const char *victim_abspath; 8486 const char *merge_source_abspath; 8487 svn_wc_conflict_reason_t local_change; 8488 svn_wc_operation_t operation; 8489 const char *lock_abspath; 8490 svn_error_t *err; 8491 const char *repos_root_url; 8492 const char *incoming_old_repos_relpath; 8493 svn_revnum_t incoming_old_pegrev; 8494 const char *incoming_new_repos_relpath; 8495 svn_revnum_t incoming_new_pegrev; 8496 const char *wc_tmpdir; 8497 const char *ancestor_abspath; 8498 svn_stream_t *ancestor_stream; 8499 apr_hash_t *ancestor_props; 8500 apr_hash_t *victim_props; 8501 apr_hash_t *move_target_props; 8502 const char *ancestor_url; 8503 const char *corrected_url; 8504 svn_ra_session_t *ra_session; 8505 svn_wc_merge_outcome_t merge_content_outcome; 8506 svn_wc_notify_state_t merge_props_outcome; 8507 apr_array_header_t *propdiffs; 8508 struct conflict_tree_incoming_delete_details *details; 8509 apr_array_header_t *possible_moved_to_abspaths; 8510 const char *moved_to_abspath; 8511 const char *incoming_abspath = NULL; 8512 8513 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 8514 local_change = svn_client_conflict_get_local_change(conflict); 8515 operation = svn_client_conflict_get_operation(conflict); 8516 details = conflict->tree_conflict_incoming_details; 8517 if (details == NULL || details->moves == NULL) 8518 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8519 _("The specified conflict resolution option " 8520 "requires details for tree conflict at '%s' " 8521 "to be fetched from the repository first."), 8522 svn_dirent_local_style(victim_abspath, 8523 scratch_pool)); 8524 if (operation == svn_wc_operation_none) 8525 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 8526 _("Invalid operation code '%d' recorded for " 8527 "conflict at '%s'"), operation, 8528 svn_dirent_local_style(victim_abspath, 8529 scratch_pool)); 8530 8531 option_id = svn_client_conflict_option_get_id(option); 8532 SVN_ERR_ASSERT(option_id == 8533 svn_client_conflict_option_incoming_move_file_text_merge || 8534 option_id == 8535 svn_client_conflict_option_both_moved_file_move_merge); 8536 8537 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 8538 conflict, scratch_pool, 8539 scratch_pool)); 8540 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 8541 &incoming_old_repos_relpath, &incoming_old_pegrev, 8542 NULL, conflict, scratch_pool, 8543 scratch_pool)); 8544 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 8545 &incoming_new_repos_relpath, &incoming_new_pegrev, 8546 NULL, conflict, scratch_pool, 8547 scratch_pool)); 8548 8549 /* Set up temporary storage for the common ancestor version of the file. */ 8550 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, 8551 scratch_pool, scratch_pool)); 8552 SVN_ERR(svn_stream_open_unique(&ancestor_stream, 8553 &ancestor_abspath, wc_tmpdir, 8554 svn_io_file_del_on_pool_cleanup, 8555 scratch_pool, scratch_pool)); 8556 8557 /* Fetch the ancestor file's content. */ 8558 ancestor_url = svn_path_url_add_component2(repos_root_url, 8559 incoming_old_repos_relpath, 8560 scratch_pool); 8561 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 8562 ancestor_url, NULL, NULL, 8563 FALSE, FALSE, ctx, 8564 scratch_pool, scratch_pool)); 8565 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, 8566 ancestor_stream, NULL, /* fetched_rev */ 8567 &ancestor_props, scratch_pool)); 8568 filter_props(ancestor_props, scratch_pool); 8569 8570 /* Close stream to flush ancestor file to disk. */ 8571 SVN_ERR(svn_stream_close(ancestor_stream)); 8572 8573 possible_moved_to_abspaths = 8574 svn_hash_gets(details->wc_move_targets, 8575 get_moved_to_repos_relpath(details, scratch_pool)); 8576 moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths, 8577 details->wc_move_target_idx, 8578 const char *); 8579 8580 if (local_change == svn_wc_conflict_reason_missing) 8581 { 8582 /* This is an incoming move vs local move conflict. 8583 * Merge from the local move's target location to the 8584 * incoming move's target location. */ 8585 struct conflict_tree_local_missing_details *local_details; 8586 apr_array_header_t *moves; 8587 8588 local_details = conflict->tree_conflict_local_details; 8589 moves = svn_hash_gets(local_details->wc_move_targets, 8590 local_details->move_target_repos_relpath); 8591 merge_source_abspath = 8592 APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, const char *); 8593 } 8594 else 8595 merge_source_abspath = victim_abspath; 8596 8597 /* ### The following WC modifications should be atomic. */ 8598 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 8599 &lock_abspath, ctx->wc_ctx, 8600 svn_dirent_get_longest_ancestor(victim_abspath, 8601 moved_to_abspath, 8602 scratch_pool), 8603 scratch_pool, scratch_pool)); 8604 8605 if (local_change != svn_wc_conflict_reason_missing) 8606 { 8607 err = verify_local_state_for_incoming_delete(conflict, option, ctx, 8608 scratch_pool); 8609 if (err) 8610 goto unlock_wc; 8611 } 8612 8613 /* Get a copy of the conflict victim's properties. */ 8614 err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, merge_source_abspath, 8615 scratch_pool, scratch_pool); 8616 if (err) 8617 goto unlock_wc; 8618 8619 /* Get a copy of the move target's properties. */ 8620 err = svn_wc_prop_list2(&move_target_props, ctx->wc_ctx, 8621 moved_to_abspath, 8622 scratch_pool, scratch_pool); 8623 if (err) 8624 goto unlock_wc; 8625 8626 /* Create a property diff for the files. */ 8627 err = svn_prop_diffs(&propdiffs, move_target_props, victim_props, 8628 scratch_pool); 8629 if (err) 8630 goto unlock_wc; 8631 8632 if (operation == svn_wc_operation_update || 8633 operation == svn_wc_operation_switch) 8634 { 8635 svn_stream_t *moved_to_stream; 8636 svn_stream_t *incoming_stream; 8637 8638 /* Create a temporary copy of the moved file in repository-normal form. 8639 * Set up this temporary file to be automatically removed. */ 8640 err = svn_stream_open_unique(&incoming_stream, 8641 &incoming_abspath, wc_tmpdir, 8642 svn_io_file_del_on_pool_cleanup, 8643 scratch_pool, scratch_pool); 8644 if (err) 8645 goto unlock_wc; 8646 8647 err = svn_wc__translated_stream(&moved_to_stream, ctx->wc_ctx, 8648 moved_to_abspath, 8649 moved_to_abspath, 8650 SVN_WC_TRANSLATE_TO_NF, 8651 scratch_pool, scratch_pool); 8652 if (err) 8653 goto unlock_wc; 8654 8655 err = svn_stream_copy3(moved_to_stream, incoming_stream, 8656 NULL, NULL, /* no cancellation */ 8657 scratch_pool); 8658 if (err) 8659 goto unlock_wc; 8660 8661 /* Overwrite the moved file with the conflict victim's content. 8662 * Incoming changes will be merged in from the temporary file created 8663 * above. This is required to correctly make local changes show up as 8664 * 'mine' during the three-way text merge between the ancestor file, 8665 * the conflict victim ('mine'), and the moved file ('theirs') which 8666 * was brought in by the update/switch operation and occupies the path 8667 * of the merge target. */ 8668 err = svn_io_copy_file(merge_source_abspath, moved_to_abspath, FALSE, 8669 scratch_pool); 8670 if (err) 8671 goto unlock_wc; 8672 } 8673 else if (operation == svn_wc_operation_merge) 8674 { 8675 svn_stream_t *incoming_stream; 8676 svn_stream_t *move_target_stream; 8677 8678 /* Set aside the current move target file. This is required to apply 8679 * the move, and only then perform a three-way text merge between 8680 * the ancestor's file, our working file (which we would move to 8681 * the destination), and the file that we have set aside, which 8682 * contains the incoming fulltext. 8683 * Set up this temporary file to NOT be automatically removed. */ 8684 err = svn_stream_open_unique(&incoming_stream, 8685 &incoming_abspath, wc_tmpdir, 8686 svn_io_file_del_none, 8687 scratch_pool, scratch_pool); 8688 if (err) 8689 goto unlock_wc; 8690 8691 err = svn_wc__translated_stream(&move_target_stream, ctx->wc_ctx, 8692 moved_to_abspath, moved_to_abspath, 8693 SVN_WC_TRANSLATE_TO_NF, 8694 scratch_pool, scratch_pool); 8695 if (err) 8696 goto unlock_wc; 8697 8698 err = svn_stream_copy3(move_target_stream, incoming_stream, 8699 NULL, NULL, /* no cancellation */ 8700 scratch_pool); 8701 if (err) 8702 goto unlock_wc; 8703 8704 /* Apply the incoming move. */ 8705 err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool); 8706 if (err) 8707 goto unlock_wc; 8708 err = svn_wc__move2(ctx->wc_ctx, merge_source_abspath, moved_to_abspath, 8709 FALSE, /* ordinary (not meta-data only) move */ 8710 FALSE, /* mixed-revisions don't apply to files */ 8711 NULL, NULL, /* don't allow user to cancel here */ 8712 NULL, NULL, /* no extra notification */ 8713 scratch_pool); 8714 if (err) 8715 goto unlock_wc; 8716 } 8717 else 8718 SVN_ERR_MALFUNCTION(); 8719 8720 /* Perform the file merge. */ 8721 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, 8722 ctx->wc_ctx, ancestor_abspath, 8723 incoming_abspath, moved_to_abspath, 8724 NULL, NULL, NULL, /* labels */ 8725 NULL, NULL, /* conflict versions */ 8726 FALSE, /* dry run */ 8727 NULL, NULL, /* diff3_cmd, merge_options */ 8728 apr_hash_count(ancestor_props) ? ancestor_props : NULL, 8729 propdiffs, 8730 NULL, NULL, /* conflict func/baton */ 8731 NULL, NULL, /* don't allow user to cancel here */ 8732 scratch_pool); 8733 svn_io_sleep_for_timestamps(moved_to_abspath, scratch_pool); 8734 if (err) 8735 goto unlock_wc; 8736 8737 if (operation == svn_wc_operation_merge && incoming_abspath) 8738 { 8739 err = svn_io_remove_file2(incoming_abspath, TRUE, scratch_pool); 8740 if (err) 8741 goto unlock_wc; 8742 incoming_abspath = NULL; 8743 } 8744 8745 if (ctx->notify_func2) 8746 { 8747 svn_wc_notify_t *notify; 8748 8749 /* Tell the world about the file merge that just happened. */ 8750 notify = svn_wc_create_notify(moved_to_abspath, 8751 svn_wc_notify_update_update, 8752 scratch_pool); 8753 if (merge_content_outcome == svn_wc_merge_conflict) 8754 notify->content_state = svn_wc_notify_state_conflicted; 8755 else 8756 notify->content_state = svn_wc_notify_state_merged; 8757 notify->prop_state = merge_props_outcome; 8758 notify->kind = svn_node_file; 8759 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 8760 } 8761 8762 if (operation == svn_wc_operation_update || 8763 operation == svn_wc_operation_switch) 8764 { 8765 /* Delete the tree conflict victim (clears the tree conflict marker). */ 8766 err = svn_wc_delete4(ctx->wc_ctx, victim_abspath, FALSE, FALSE, 8767 NULL, NULL, /* don't allow user to cancel here */ 8768 NULL, NULL, /* no extra notification */ 8769 scratch_pool); 8770 if (err) 8771 goto unlock_wc; 8772 } 8773 else if (local_change == svn_wc_conflict_reason_missing) 8774 { 8775 /* Clear tree conflict marker. */ 8776 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, 8777 scratch_pool); 8778 if (err) 8779 goto unlock_wc; 8780 } 8781 8782 if (ctx->notify_func2) 8783 { 8784 svn_wc_notify_t *notify; 8785 8786 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, 8787 scratch_pool); 8788 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 8789 } 8790 8791 conflict->resolution_tree = option_id; 8792 8793unlock_wc: 8794 if (err && operation == svn_wc_operation_merge && incoming_abspath) 8795 err = svn_error_quick_wrapf( 8796 err, _("If needed, a backup copy of '%s' can be found at '%s'"), 8797 svn_dirent_local_style(moved_to_abspath, scratch_pool), 8798 svn_dirent_local_style(incoming_abspath, scratch_pool)); 8799 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 8800 lock_abspath, 8801 scratch_pool)); 8802 SVN_ERR(err); 8803 8804 return SVN_NO_ERROR; 8805} 8806 8807/* Implements conflict_option_resolve_func_t. 8808 * Resolve an incoming move vs local move conflict by merging from the 8809 * incoming move's target location to the local move's target location, 8810 * overriding the incoming move. */ 8811static svn_error_t * 8812resolve_both_moved_file_text_merge(svn_client_conflict_option_t *option, 8813 svn_client_conflict_t *conflict, 8814 svn_client_ctx_t *ctx, 8815 apr_pool_t *scratch_pool) 8816{ 8817 svn_client_conflict_option_id_t option_id; 8818 const char *victim_abspath; 8819 const char *local_moved_to_abspath; 8820 svn_wc_operation_t operation; 8821 const char *lock_abspath; 8822 svn_error_t *err; 8823 const char *repos_root_url; 8824 const char *incoming_old_repos_relpath; 8825 svn_revnum_t incoming_old_pegrev; 8826 const char *incoming_new_repos_relpath; 8827 svn_revnum_t incoming_new_pegrev; 8828 const char *wc_tmpdir; 8829 const char *ancestor_abspath; 8830 svn_stream_t *ancestor_stream; 8831 apr_hash_t *ancestor_props; 8832 apr_hash_t *incoming_props; 8833 apr_hash_t *local_props; 8834 const char *ancestor_url; 8835 const char *corrected_url; 8836 svn_ra_session_t *ra_session; 8837 svn_wc_merge_outcome_t merge_content_outcome; 8838 svn_wc_notify_state_t merge_props_outcome; 8839 apr_array_header_t *propdiffs; 8840 struct conflict_tree_incoming_delete_details *incoming_details; 8841 apr_array_header_t *possible_moved_to_abspaths; 8842 const char *incoming_moved_to_abspath; 8843 struct conflict_tree_local_missing_details *local_details; 8844 apr_array_header_t *local_moves; 8845 8846 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 8847 operation = svn_client_conflict_get_operation(conflict); 8848 incoming_details = conflict->tree_conflict_incoming_details; 8849 if (incoming_details == NULL || incoming_details->moves == NULL) 8850 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 8851 _("The specified conflict resolution option " 8852 "requires details for tree conflict at '%s' " 8853 "to be fetched from the repository first."), 8854 svn_dirent_local_style(victim_abspath, 8855 scratch_pool)); 8856 if (operation == svn_wc_operation_none) 8857 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 8858 _("Invalid operation code '%d' recorded for " 8859 "conflict at '%s'"), operation, 8860 svn_dirent_local_style(victim_abspath, 8861 scratch_pool)); 8862 8863 option_id = svn_client_conflict_option_get_id(option); 8864 SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge); 8865 8866 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 8867 conflict, scratch_pool, 8868 scratch_pool)); 8869 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 8870 &incoming_old_repos_relpath, &incoming_old_pegrev, 8871 NULL, conflict, scratch_pool, 8872 scratch_pool)); 8873 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 8874 &incoming_new_repos_relpath, &incoming_new_pegrev, 8875 NULL, conflict, scratch_pool, 8876 scratch_pool)); 8877 8878 /* Set up temporary storage for the common ancestor version of the file. */ 8879 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, 8880 scratch_pool, scratch_pool)); 8881 SVN_ERR(svn_stream_open_unique(&ancestor_stream, 8882 &ancestor_abspath, wc_tmpdir, 8883 svn_io_file_del_on_pool_cleanup, 8884 scratch_pool, scratch_pool)); 8885 8886 /* Fetch the ancestor file's content. */ 8887 ancestor_url = svn_path_url_add_component2(repos_root_url, 8888 incoming_old_repos_relpath, 8889 scratch_pool); 8890 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 8891 ancestor_url, NULL, NULL, 8892 FALSE, FALSE, ctx, 8893 scratch_pool, scratch_pool)); 8894 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, 8895 ancestor_stream, NULL, /* fetched_rev */ 8896 &ancestor_props, scratch_pool)); 8897 filter_props(ancestor_props, scratch_pool); 8898 8899 /* Close stream to flush ancestor file to disk. */ 8900 SVN_ERR(svn_stream_close(ancestor_stream)); 8901 8902 possible_moved_to_abspaths = 8903 svn_hash_gets(incoming_details->wc_move_targets, 8904 get_moved_to_repos_relpath(incoming_details, scratch_pool)); 8905 incoming_moved_to_abspath = 8906 APR_ARRAY_IDX(possible_moved_to_abspaths, 8907 incoming_details->wc_move_target_idx, const char *); 8908 8909 local_details = conflict->tree_conflict_local_details; 8910 local_moves = svn_hash_gets(local_details->wc_move_targets, 8911 local_details->move_target_repos_relpath); 8912 local_moved_to_abspath = 8913 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); 8914 8915 /* ### The following WC modifications should be atomic. */ 8916 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 8917 &lock_abspath, ctx->wc_ctx, 8918 svn_dirent_get_longest_ancestor(victim_abspath, 8919 local_moved_to_abspath, 8920 scratch_pool), 8921 scratch_pool, scratch_pool)); 8922 8923 /* Get a copy of the incoming moved item's properties. */ 8924 err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, 8925 incoming_moved_to_abspath, 8926 scratch_pool, scratch_pool); 8927 if (err) 8928 goto unlock_wc; 8929 8930 /* Get a copy of the local move target's properties. */ 8931 err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, 8932 local_moved_to_abspath, 8933 scratch_pool, scratch_pool); 8934 if (err) 8935 goto unlock_wc; 8936 8937 /* Create a property diff for the files. */ 8938 err = svn_prop_diffs(&propdiffs, incoming_props, local_props, 8939 scratch_pool); 8940 if (err) 8941 goto unlock_wc; 8942 8943 /* Perform the file merge. */ 8944 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, 8945 ctx->wc_ctx, ancestor_abspath, 8946 incoming_moved_to_abspath, local_moved_to_abspath, 8947 NULL, NULL, NULL, /* labels */ 8948 NULL, NULL, /* conflict versions */ 8949 FALSE, /* dry run */ 8950 NULL, NULL, /* diff3_cmd, merge_options */ 8951 apr_hash_count(ancestor_props) ? ancestor_props : NULL, 8952 propdiffs, 8953 NULL, NULL, /* conflict func/baton */ 8954 NULL, NULL, /* don't allow user to cancel here */ 8955 scratch_pool); 8956 if (err) 8957 goto unlock_wc; 8958 8959 if (ctx->notify_func2) 8960 { 8961 svn_wc_notify_t *notify; 8962 8963 /* Tell the world about the file merge that just happened. */ 8964 notify = svn_wc_create_notify(local_moved_to_abspath, 8965 svn_wc_notify_update_update, 8966 scratch_pool); 8967 if (merge_content_outcome == svn_wc_merge_conflict) 8968 notify->content_state = svn_wc_notify_state_conflicted; 8969 else 8970 notify->content_state = svn_wc_notify_state_merged; 8971 notify->prop_state = merge_props_outcome; 8972 notify->kind = svn_node_file; 8973 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 8974 } 8975 8976 /* Revert local addition of the incoming move's target. */ 8977 err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, 8978 svn_depth_infinity, FALSE, NULL, TRUE, FALSE, 8979 FALSE /*added_keep_local*/, 8980 NULL, NULL, /* no cancellation */ 8981 ctx->notify_func2, ctx->notify_baton2, 8982 scratch_pool); 8983 if (err) 8984 goto unlock_wc; 8985 8986 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); 8987 if (err) 8988 goto unlock_wc; 8989 8990 if (ctx->notify_func2) 8991 { 8992 svn_wc_notify_t *notify; 8993 8994 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, 8995 scratch_pool); 8996 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 8997 } 8998 8999 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); 9000 9001 conflict->resolution_tree = option_id; 9002 9003unlock_wc: 9004 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 9005 lock_abspath, 9006 scratch_pool)); 9007 SVN_ERR(err); 9008 9009 return SVN_NO_ERROR; 9010} 9011 9012/* Implements conflict_option_resolve_func_t. 9013 * Resolve an incoming move vs local move conflict by moving the locally moved 9014 * directory to the incoming move target location, and then merging changes. */ 9015static svn_error_t * 9016resolve_both_moved_dir_merge(svn_client_conflict_option_t *option, 9017 svn_client_conflict_t *conflict, 9018 svn_client_ctx_t *ctx, 9019 apr_pool_t *scratch_pool) 9020{ 9021 svn_client_conflict_option_id_t option_id; 9022 const char *victim_abspath; 9023 const char *local_moved_to_abspath; 9024 svn_wc_operation_t operation; 9025 const char *lock_abspath; 9026 svn_error_t *err; 9027 const char *repos_root_url; 9028 const char *incoming_old_repos_relpath; 9029 svn_revnum_t incoming_old_pegrev; 9030 const char *incoming_new_repos_relpath; 9031 svn_revnum_t incoming_new_pegrev; 9032 const char *incoming_moved_repos_relpath; 9033 struct conflict_tree_incoming_delete_details *incoming_details; 9034 apr_array_header_t *possible_moved_to_abspaths; 9035 const char *incoming_moved_to_abspath; 9036 struct conflict_tree_local_missing_details *local_details; 9037 apr_array_header_t *local_moves; 9038 svn_client__conflict_report_t *conflict_report; 9039 const char *incoming_old_url; 9040 const char *incoming_moved_url; 9041 svn_opt_revision_t incoming_old_opt_rev; 9042 svn_opt_revision_t incoming_moved_opt_rev; 9043 9044 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 9045 operation = svn_client_conflict_get_operation(conflict); 9046 incoming_details = conflict->tree_conflict_incoming_details; 9047 if (incoming_details == NULL || incoming_details->moves == NULL) 9048 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 9049 _("The specified conflict resolution option " 9050 "requires details for tree conflict at '%s' " 9051 9052 "to be fetched from the repository first."), 9053 svn_dirent_local_style(victim_abspath, 9054 scratch_pool)); 9055 if (operation == svn_wc_operation_none) 9056 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 9057 _("Invalid operation code '%d' recorded for " 9058 "conflict at '%s'"), operation, 9059 svn_dirent_local_style(victim_abspath, 9060 scratch_pool)); 9061 9062 option_id = svn_client_conflict_option_get_id(option); 9063 SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_dir_merge); 9064 9065 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 9066 conflict, scratch_pool, 9067 scratch_pool)); 9068 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 9069 &incoming_old_repos_relpath, &incoming_old_pegrev, 9070 NULL, conflict, scratch_pool, 9071 scratch_pool)); 9072 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 9073 &incoming_new_repos_relpath, &incoming_new_pegrev, 9074 NULL, conflict, scratch_pool, 9075 scratch_pool)); 9076 9077 possible_moved_to_abspaths = 9078 svn_hash_gets(incoming_details->wc_move_targets, 9079 get_moved_to_repos_relpath(incoming_details, scratch_pool)); 9080 incoming_moved_to_abspath = 9081 APR_ARRAY_IDX(possible_moved_to_abspaths, 9082 incoming_details->wc_move_target_idx, const char *); 9083 9084 local_details = conflict->tree_conflict_local_details; 9085 local_moves = svn_hash_gets(local_details->wc_move_targets, 9086 local_details->move_target_repos_relpath); 9087 local_moved_to_abspath = 9088 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); 9089 9090 /* ### The following WC modifications should be atomic. */ 9091 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 9092 &lock_abspath, ctx->wc_ctx, 9093 svn_dirent_get_longest_ancestor(victim_abspath, 9094 local_moved_to_abspath, 9095 scratch_pool), 9096 scratch_pool, scratch_pool)); 9097 9098 /* Perform the merge. */ 9099 incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", 9100 incoming_old_repos_relpath, SVN_VA_NULL); 9101 incoming_old_opt_rev.kind = svn_opt_revision_number; 9102 incoming_old_opt_rev.value.number = incoming_old_pegrev; 9103 9104 incoming_moved_repos_relpath = 9105 get_moved_to_repos_relpath(incoming_details, scratch_pool); 9106 incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/", 9107 incoming_moved_repos_relpath, SVN_VA_NULL); 9108 incoming_moved_opt_rev.kind = svn_opt_revision_number; 9109 incoming_moved_opt_rev.value.number = incoming_new_pegrev; 9110 err = svn_client__merge_locked(&conflict_report, 9111 incoming_old_url, &incoming_old_opt_rev, 9112 incoming_moved_url, &incoming_moved_opt_rev, 9113 local_moved_to_abspath, svn_depth_infinity, 9114 TRUE, TRUE, /* do a no-ancestry merge */ 9115 FALSE, FALSE, FALSE, 9116 TRUE, /* Allow mixed-rev just in case, 9117 * since conflict victims can't be 9118 * updated to straighten out 9119 * mixed-rev trees. */ 9120 NULL, ctx, scratch_pool, scratch_pool); 9121 if (err) 9122 goto unlock_wc; 9123 9124 /* Revert local addition of the incoming move's target. */ 9125 err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, 9126 svn_depth_infinity, FALSE, NULL, TRUE, FALSE, 9127 FALSE /*added_keep_local*/, 9128 NULL, NULL, /* no cancellation */ 9129 ctx->notify_func2, ctx->notify_baton2, 9130 scratch_pool); 9131 if (err) 9132 goto unlock_wc; 9133 9134 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); 9135 if (err) 9136 goto unlock_wc; 9137 9138 if (ctx->notify_func2) 9139 { 9140 svn_wc_notify_t *notify; 9141 9142 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, 9143 scratch_pool); 9144 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 9145 } 9146 9147 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); 9148 9149 conflict->resolution_tree = option_id; 9150 9151unlock_wc: 9152 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 9153 lock_abspath, 9154 scratch_pool)); 9155 SVN_ERR(err); 9156 9157 return SVN_NO_ERROR; 9158} 9159 9160/* Implements conflict_option_resolve_func_t. 9161 * Resolve an incoming move vs local move conflict by merging from the 9162 * incoming move's target location to the local move's target location, 9163 * overriding the incoming move. */ 9164static svn_error_t * 9165resolve_both_moved_dir_move_merge(svn_client_conflict_option_t *option, 9166 svn_client_conflict_t *conflict, 9167 svn_client_ctx_t *ctx, 9168 apr_pool_t *scratch_pool) 9169{ 9170 svn_client_conflict_option_id_t option_id; 9171 const char *victim_abspath; 9172 const char *local_moved_to_abspath; 9173 svn_wc_operation_t operation; 9174 const char *lock_abspath; 9175 svn_error_t *err; 9176 const char *repos_root_url; 9177 const char *incoming_old_repos_relpath; 9178 svn_revnum_t incoming_old_pegrev; 9179 const char *incoming_new_repos_relpath; 9180 svn_revnum_t incoming_new_pegrev; 9181 struct conflict_tree_incoming_delete_details *incoming_details; 9182 apr_array_header_t *possible_moved_to_abspaths; 9183 const char *incoming_moved_to_abspath; 9184 struct conflict_tree_local_missing_details *local_details; 9185 apr_array_header_t *local_moves; 9186 svn_client__conflict_report_t *conflict_report; 9187 const char *incoming_old_url; 9188 const char *incoming_moved_url; 9189 svn_opt_revision_t incoming_old_opt_rev; 9190 svn_opt_revision_t incoming_moved_opt_rev; 9191 9192 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 9193 operation = svn_client_conflict_get_operation(conflict); 9194 incoming_details = conflict->tree_conflict_incoming_details; 9195 if (incoming_details == NULL || incoming_details->moves == NULL) 9196 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 9197 _("The specified conflict resolution option " 9198 "requires details for tree conflict at '%s' " 9199 9200 "to be fetched from the repository first."), 9201 svn_dirent_local_style(victim_abspath, 9202 scratch_pool)); 9203 if (operation == svn_wc_operation_none) 9204 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 9205 _("Invalid operation code '%d' recorded for " 9206 "conflict at '%s'"), operation, 9207 svn_dirent_local_style(victim_abspath, 9208 scratch_pool)); 9209 9210 option_id = svn_client_conflict_option_get_id(option); 9211 SVN_ERR_ASSERT(option_id == 9212 svn_client_conflict_option_both_moved_dir_move_merge); 9213 9214 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 9215 conflict, scratch_pool, 9216 scratch_pool)); 9217 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 9218 &incoming_old_repos_relpath, &incoming_old_pegrev, 9219 NULL, conflict, scratch_pool, 9220 scratch_pool)); 9221 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 9222 &incoming_new_repos_relpath, &incoming_new_pegrev, 9223 NULL, conflict, scratch_pool, 9224 scratch_pool)); 9225 9226 possible_moved_to_abspaths = 9227 svn_hash_gets(incoming_details->wc_move_targets, 9228 get_moved_to_repos_relpath(incoming_details, scratch_pool)); 9229 incoming_moved_to_abspath = 9230 APR_ARRAY_IDX(possible_moved_to_abspaths, 9231 incoming_details->wc_move_target_idx, const char *); 9232 9233 local_details = conflict->tree_conflict_local_details; 9234 local_moves = svn_hash_gets(local_details->wc_move_targets, 9235 local_details->move_target_repos_relpath); 9236 local_moved_to_abspath = 9237 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); 9238 9239 /* ### The following WC modifications should be atomic. */ 9240 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 9241 &lock_abspath, ctx->wc_ctx, 9242 svn_dirent_get_longest_ancestor(victim_abspath, 9243 local_moved_to_abspath, 9244 scratch_pool), 9245 scratch_pool, scratch_pool)); 9246 9247 /* Revert the incoming move target directory. */ 9248 err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, 9249 svn_depth_infinity, 9250 FALSE, NULL, TRUE, FALSE, 9251 TRUE /*added_keep_local*/, 9252 NULL, NULL, /* no cancellation */ 9253 ctx->notify_func2, ctx->notify_baton2, 9254 scratch_pool); 9255 if (err) 9256 goto unlock_wc; 9257 9258 /* The move operation is not part of natural history. We must replicate 9259 * this move in our history. Record a move in the working copy. */ 9260 err = svn_wc__move2(ctx->wc_ctx, local_moved_to_abspath, 9261 incoming_moved_to_abspath, 9262 FALSE, /* this is not a meta-data only move */ 9263 TRUE, /* allow mixed-revisions just in case */ 9264 NULL, NULL, /* don't allow user to cancel here */ 9265 ctx->notify_func2, ctx->notify_baton2, 9266 scratch_pool); 9267 if (err) 9268 goto unlock_wc; 9269 9270 /* Merge INCOMING_OLD_URL@MERGE_LEFT->INCOMING_MOVED_URL@MERGE_RIGHT 9271 * into the locally moved merge target. */ 9272 incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", 9273 incoming_old_repos_relpath, SVN_VA_NULL); 9274 incoming_old_opt_rev.kind = svn_opt_revision_number; 9275 incoming_old_opt_rev.value.number = incoming_old_pegrev; 9276 9277 incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/", 9278 incoming_details->move_target_repos_relpath, 9279 SVN_VA_NULL); 9280 incoming_moved_opt_rev.kind = svn_opt_revision_number; 9281 incoming_moved_opt_rev.value.number = incoming_new_pegrev; 9282 err = svn_client__merge_locked(&conflict_report, 9283 incoming_old_url, &incoming_old_opt_rev, 9284 incoming_moved_url, &incoming_moved_opt_rev, 9285 incoming_moved_to_abspath, svn_depth_infinity, 9286 TRUE, TRUE, /* do a no-ancestry merge */ 9287 FALSE, FALSE, FALSE, 9288 TRUE, /* Allow mixed-rev just in case, 9289 * since conflict victims can't be 9290 * updated to straighten out 9291 * mixed-rev trees. */ 9292 NULL, ctx, scratch_pool, scratch_pool); 9293 if (err) 9294 goto unlock_wc; 9295 9296 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); 9297 if (err) 9298 goto unlock_wc; 9299 9300 if (ctx->notify_func2) 9301 { 9302 svn_wc_notify_t *notify; 9303 9304 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, 9305 scratch_pool); 9306 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 9307 } 9308 9309 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); 9310 9311 conflict->resolution_tree = option_id; 9312 9313unlock_wc: 9314 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 9315 lock_abspath, 9316 scratch_pool)); 9317 SVN_ERR(err); 9318 9319 return SVN_NO_ERROR; 9320} 9321 9322/* Implements conflict_option_resolve_func_t. */ 9323static svn_error_t * 9324resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, 9325 svn_client_conflict_t *conflict, 9326 svn_client_ctx_t *ctx, 9327 apr_pool_t *scratch_pool) 9328{ 9329 svn_client_conflict_option_id_t option_id; 9330 const char *local_abspath; 9331 svn_wc_operation_t operation; 9332 const char *lock_abspath; 9333 svn_error_t *err; 9334 const char *repos_root_url; 9335 const char *repos_uuid; 9336 const char *incoming_old_repos_relpath; 9337 svn_revnum_t incoming_old_pegrev; 9338 const char *incoming_new_repos_relpath; 9339 svn_revnum_t incoming_new_pegrev; 9340 const char *victim_repos_relpath; 9341 svn_revnum_t victim_peg_rev; 9342 const char *moved_to_repos_relpath; 9343 svn_revnum_t moved_to_peg_rev; 9344 struct conflict_tree_incoming_delete_details *details; 9345 apr_array_header_t *possible_moved_to_abspaths; 9346 const char *moved_to_abspath; 9347 const char *incoming_old_url; 9348 svn_opt_revision_t incoming_old_opt_rev; 9349 svn_client__conflict_report_t *conflict_report; 9350 svn_boolean_t is_copy; 9351 svn_boolean_t is_modified; 9352 9353 local_abspath = svn_client_conflict_get_local_abspath(conflict); 9354 operation = svn_client_conflict_get_operation(conflict); 9355 details = conflict->tree_conflict_incoming_details; 9356 if (details == NULL || details->moves == NULL) 9357 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 9358 _("The specified conflict resolution option " 9359 "requires details for tree conflict at '%s' " 9360 "to be fetched from the repository first."), 9361 svn_dirent_local_style(local_abspath, 9362 scratch_pool)); 9363 9364 option_id = svn_client_conflict_option_get_id(option); 9365 SVN_ERR_ASSERT(option_id == 9366 svn_client_conflict_option_incoming_move_dir_merge); 9367 9368 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid, 9369 conflict, scratch_pool, 9370 scratch_pool)); 9371 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 9372 &incoming_old_repos_relpath, &incoming_old_pegrev, 9373 NULL, conflict, scratch_pool, 9374 scratch_pool)); 9375 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 9376 &incoming_new_repos_relpath, &incoming_new_pegrev, 9377 NULL, conflict, scratch_pool, 9378 scratch_pool)); 9379 9380 /* Get repository location of the moved-away node (the conflict victim). */ 9381 if (operation == svn_wc_operation_update || 9382 operation == svn_wc_operation_switch) 9383 { 9384 victim_repos_relpath = incoming_old_repos_relpath; 9385 victim_peg_rev = incoming_old_pegrev; 9386 } 9387 else if (operation == svn_wc_operation_merge) 9388 SVN_ERR(svn_wc__node_get_repos_info(&victim_peg_rev, &victim_repos_relpath, 9389 NULL, NULL, ctx->wc_ctx, local_abspath, 9390 scratch_pool, scratch_pool)); 9391 9392 /* Get repository location of the moved-here node (incoming move). */ 9393 possible_moved_to_abspaths = 9394 svn_hash_gets(details->wc_move_targets, 9395 get_moved_to_repos_relpath(details, scratch_pool)); 9396 moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths, 9397 details->wc_move_target_idx, 9398 const char *); 9399 9400 /* ### The following WC modifications should be atomic. */ 9401 9402 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 9403 &lock_abspath, ctx->wc_ctx, 9404 svn_dirent_get_longest_ancestor(local_abspath, 9405 moved_to_abspath, 9406 scratch_pool), 9407 scratch_pool, scratch_pool)); 9408 9409 err = svn_wc__node_get_origin(&is_copy, &moved_to_peg_rev, 9410 &moved_to_repos_relpath, 9411 NULL, NULL, NULL, NULL, 9412 ctx->wc_ctx, moved_to_abspath, FALSE, 9413 scratch_pool, scratch_pool); 9414 if (err) 9415 goto unlock_wc; 9416 if (!is_copy && operation == svn_wc_operation_merge) 9417 { 9418 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 9419 _("Cannot resolve tree conflict on '%s' " 9420 "(expected a copied item at '%s', but the " 9421 "item is not a copy)"), 9422 svn_dirent_local_style(local_abspath, 9423 scratch_pool), 9424 svn_dirent_local_style(moved_to_abspath, 9425 scratch_pool)); 9426 goto unlock_wc; 9427 } 9428 9429 if (moved_to_repos_relpath == NULL || moved_to_peg_rev == SVN_INVALID_REVNUM) 9430 { 9431 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 9432 _("Cannot resolve tree conflict on '%s' " 9433 "(could not determine origin of '%s')"), 9434 svn_dirent_local_style(local_abspath, 9435 scratch_pool), 9436 svn_dirent_local_style(moved_to_abspath, 9437 scratch_pool)); 9438 goto unlock_wc; 9439 } 9440 9441 err = verify_local_state_for_incoming_delete(conflict, option, ctx, 9442 scratch_pool); 9443 if (err) 9444 goto unlock_wc; 9445 9446 if (operation == svn_wc_operation_merge) 9447 { 9448 const char *move_target_url; 9449 svn_opt_revision_t incoming_new_opt_rev; 9450 9451 /* Revert the incoming move target directory. */ 9452 err = svn_wc_revert6(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity, 9453 FALSE, NULL, TRUE, FALSE, 9454 TRUE /*added_keep_local*/, 9455 NULL, NULL, /* no cancellation */ 9456 ctx->notify_func2, ctx->notify_baton2, 9457 scratch_pool); 9458 if (err) 9459 goto unlock_wc; 9460 9461 /* The move operation is not part of natural history. We must replicate 9462 * this move in our history. Record a move in the working copy. */ 9463 err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath, 9464 FALSE, /* this is not a meta-data only move */ 9465 TRUE, /* allow mixed-revisions just in case */ 9466 NULL, NULL, /* don't allow user to cancel here */ 9467 ctx->notify_func2, ctx->notify_baton2, 9468 scratch_pool); 9469 if (err) 9470 goto unlock_wc; 9471 9472 /* Merge INCOMING_OLD_URL@MERGE_LEFT->MOVE_TARGET_URL@MERGE_RIGHT 9473 * into move target. */ 9474 incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", 9475 incoming_old_repos_relpath, SVN_VA_NULL); 9476 incoming_old_opt_rev.kind = svn_opt_revision_number; 9477 incoming_old_opt_rev.value.number = incoming_old_pegrev; 9478 move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/", 9479 get_moved_to_repos_relpath(details, 9480 scratch_pool), 9481 SVN_VA_NULL); 9482 incoming_new_opt_rev.kind = svn_opt_revision_number; 9483 incoming_new_opt_rev.value.number = incoming_new_pegrev; 9484 err = svn_client__merge_locked(&conflict_report, 9485 incoming_old_url, &incoming_old_opt_rev, 9486 move_target_url, &incoming_new_opt_rev, 9487 moved_to_abspath, svn_depth_infinity, 9488 TRUE, TRUE, /* do a no-ancestry merge */ 9489 FALSE, FALSE, FALSE, 9490 TRUE, /* Allow mixed-rev just in case, 9491 * since conflict victims can't be 9492 * updated to straighten out 9493 * mixed-rev trees. */ 9494 NULL, ctx, scratch_pool, scratch_pool); 9495 if (err) 9496 goto unlock_wc; 9497 } 9498 else 9499 { 9500 SVN_ERR_ASSERT(operation == svn_wc_operation_update || 9501 operation == svn_wc_operation_switch); 9502 9503 /* Merge local modifications into the incoming move target dir. */ 9504 err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath, 9505 TRUE, ctx->cancel_func, ctx->cancel_baton, 9506 scratch_pool); 9507 if (err) 9508 goto unlock_wc; 9509 9510 if (is_modified) 9511 { 9512 err = svn_wc__conflict_tree_update_incoming_move(ctx->wc_ctx, 9513 local_abspath, 9514 moved_to_abspath, 9515 ctx->cancel_func, 9516 ctx->cancel_baton, 9517 ctx->notify_func2, 9518 ctx->notify_baton2, 9519 scratch_pool); 9520 if (err) 9521 goto unlock_wc; 9522 } 9523 9524 /* The move operation is part of our natural history. 9525 * Delete the tree conflict victim (clears the tree conflict marker). */ 9526 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE, 9527 NULL, NULL, /* don't allow user to cancel here */ 9528 NULL, NULL, /* no extra notification */ 9529 scratch_pool); 9530 if (err) 9531 goto unlock_wc; 9532 } 9533 9534 if (ctx->notify_func2) 9535 { 9536 svn_wc_notify_t *notify; 9537 9538 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree, 9539 scratch_pool); 9540 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 9541 } 9542 9543 conflict->resolution_tree = option_id; 9544 9545unlock_wc: 9546 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 9547 lock_abspath, 9548 scratch_pool)); 9549 SVN_ERR(err); 9550 9551 return SVN_NO_ERROR; 9552} 9553 9554/* Implements conflict_option_resolve_func_t. 9555 * Handles svn_client_conflict_option_local_move_file_text_merge 9556 * and svn_client_conflict_option_sibling_move_file_text_merge. */ 9557static svn_error_t * 9558resolve_local_move_file_merge(svn_client_conflict_option_t *option, 9559 svn_client_conflict_t *conflict, 9560 svn_client_ctx_t *ctx, 9561 apr_pool_t *scratch_pool) 9562{ 9563 const char *lock_abspath; 9564 svn_error_t *err; 9565 const char *repos_root_url; 9566 const char *incoming_old_repos_relpath; 9567 svn_revnum_t incoming_old_pegrev; 9568 const char *incoming_new_repos_relpath; 9569 svn_revnum_t incoming_new_pegrev; 9570 const char *wc_tmpdir; 9571 const char *ancestor_tmp_abspath; 9572 const char *incoming_tmp_abspath; 9573 apr_hash_t *ancestor_props; 9574 apr_hash_t *incoming_props; 9575 svn_stream_t *stream; 9576 const char *url; 9577 const char *corrected_url; 9578 const char *old_session_url; 9579 svn_ra_session_t *ra_session; 9580 svn_wc_merge_outcome_t merge_content_outcome; 9581 svn_wc_notify_state_t merge_props_outcome; 9582 apr_array_header_t *propdiffs; 9583 struct conflict_tree_local_missing_details *details; 9584 const char *merge_target_abspath; 9585 const char *wcroot_abspath; 9586 9587 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 9588 conflict->local_abspath, scratch_pool, 9589 scratch_pool)); 9590 9591 details = conflict->tree_conflict_local_details; 9592 9593 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 9594 conflict, scratch_pool, 9595 scratch_pool)); 9596 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 9597 &incoming_old_repos_relpath, &incoming_old_pegrev, 9598 NULL, conflict, scratch_pool, 9599 scratch_pool)); 9600 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 9601 &incoming_new_repos_relpath, &incoming_new_pegrev, 9602 NULL, conflict, scratch_pool, 9603 scratch_pool)); 9604 9605 if (details->wc_siblings) 9606 { 9607 merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, 9608 details->preferred_sibling_idx, 9609 const char *); 9610 } 9611 else if (details->wc_move_targets && details->move_target_repos_relpath) 9612 { 9613 apr_array_header_t *moves; 9614 moves = svn_hash_gets(details->wc_move_targets, 9615 details->move_target_repos_relpath); 9616 merge_target_abspath = APR_ARRAY_IDX(moves, details->wc_move_target_idx, 9617 const char *); 9618 } 9619 else 9620 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 9621 _("Corresponding working copy node not found " 9622 "for '%s'"), 9623 svn_dirent_local_style( 9624 svn_dirent_skip_ancestor( 9625 wcroot_abspath, conflict->local_abspath), 9626 scratch_pool)); 9627 9628 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, 9629 merge_target_abspath, 9630 scratch_pool, scratch_pool)); 9631 9632 /* Fetch the common ancestor file's content. */ 9633 SVN_ERR(svn_stream_open_unique(&stream, &ancestor_tmp_abspath, wc_tmpdir, 9634 svn_io_file_del_on_pool_cleanup, 9635 scratch_pool, scratch_pool)); 9636 url = svn_path_url_add_component2(repos_root_url, 9637 incoming_old_repos_relpath, 9638 scratch_pool); 9639 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 9640 url, NULL, NULL, 9641 FALSE, FALSE, ctx, 9642 scratch_pool, scratch_pool)); 9643 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, stream, NULL, 9644 &ancestor_props, scratch_pool)); 9645 filter_props(ancestor_props, scratch_pool); 9646 9647 /* Close stream to flush the file to disk. */ 9648 SVN_ERR(svn_stream_close(stream)); 9649 9650 /* Do the same for the incoming file's content. */ 9651 SVN_ERR(svn_stream_open_unique(&stream, &incoming_tmp_abspath, wc_tmpdir, 9652 svn_io_file_del_on_pool_cleanup, 9653 scratch_pool, scratch_pool)); 9654 url = svn_path_url_add_component2(repos_root_url, 9655 incoming_new_repos_relpath, 9656 scratch_pool); 9657 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, 9658 url, scratch_pool)); 9659 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, stream, NULL, 9660 &incoming_props, scratch_pool)); 9661 /* Close stream to flush the file to disk. */ 9662 SVN_ERR(svn_stream_close(stream)); 9663 9664 filter_props(incoming_props, scratch_pool); 9665 9666 /* Create a property diff for the files. */ 9667 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_props, ancestor_props, 9668 scratch_pool)); 9669 9670 /* ### The following WC modifications should be atomic. */ 9671 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 9672 &lock_abspath, ctx->wc_ctx, 9673 svn_dirent_get_longest_ancestor(conflict->local_abspath, 9674 merge_target_abspath, 9675 scratch_pool), 9676 scratch_pool, scratch_pool)); 9677 9678 /* Perform the file merge. */ 9679 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, 9680 ctx->wc_ctx, 9681 ancestor_tmp_abspath, incoming_tmp_abspath, 9682 merge_target_abspath, 9683 NULL, NULL, NULL, /* labels */ 9684 NULL, NULL, /* conflict versions */ 9685 FALSE, /* dry run */ 9686 NULL, NULL, /* diff3_cmd, merge_options */ 9687 apr_hash_count(ancestor_props) ? ancestor_props : NULL, 9688 propdiffs, 9689 NULL, NULL, /* conflict func/baton */ 9690 NULL, NULL, /* don't allow user to cancel here */ 9691 scratch_pool); 9692 svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); 9693 if (err) 9694 return svn_error_compose_create(err, 9695 svn_wc__release_write_lock(ctx->wc_ctx, 9696 lock_abspath, 9697 scratch_pool)); 9698 9699 err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath, 9700 scratch_pool); 9701 err = svn_error_compose_create(err, 9702 svn_wc__release_write_lock(ctx->wc_ctx, 9703 lock_abspath, 9704 scratch_pool)); 9705 if (err) 9706 return svn_error_trace(err); 9707 9708 if (ctx->notify_func2) 9709 { 9710 svn_wc_notify_t *notify; 9711 9712 /* Tell the world about the file merge that just happened. */ 9713 notify = svn_wc_create_notify(merge_target_abspath, 9714 svn_wc_notify_update_update, 9715 scratch_pool); 9716 if (merge_content_outcome == svn_wc_merge_conflict) 9717 notify->content_state = svn_wc_notify_state_conflicted; 9718 else 9719 notify->content_state = svn_wc_notify_state_merged; 9720 notify->prop_state = merge_props_outcome; 9721 notify->kind = svn_node_file; 9722 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 9723 9724 /* And also about the successfully resolved tree conflict. */ 9725 notify = svn_wc_create_notify(conflict->local_abspath, 9726 svn_wc_notify_resolved_tree, 9727 scratch_pool); 9728 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 9729 } 9730 9731 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 9732 9733 return SVN_NO_ERROR; 9734} 9735 9736/* Implements conflict_option_resolve_func_t. */ 9737static svn_error_t * 9738resolve_local_move_dir_merge(svn_client_conflict_option_t *option, 9739 svn_client_conflict_t *conflict, 9740 svn_client_ctx_t *ctx, 9741 apr_pool_t *scratch_pool) 9742{ 9743 const char *lock_abspath; 9744 svn_error_t *err; 9745 const char *repos_root_url; 9746 const char *incoming_old_repos_relpath; 9747 svn_revnum_t incoming_old_pegrev; 9748 const char *incoming_new_repos_relpath; 9749 svn_revnum_t incoming_new_pegrev; 9750 struct conflict_tree_local_missing_details *details; 9751 const char *merge_target_abspath; 9752 const char *incoming_old_url; 9753 const char *incoming_new_url; 9754 svn_opt_revision_t incoming_old_opt_rev; 9755 svn_opt_revision_t incoming_new_opt_rev; 9756 svn_client__conflict_report_t *conflict_report; 9757 9758 details = conflict->tree_conflict_local_details; 9759 9760 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 9761 conflict, scratch_pool, 9762 scratch_pool)); 9763 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 9764 &incoming_old_repos_relpath, &incoming_old_pegrev, 9765 NULL, conflict, scratch_pool, 9766 scratch_pool)); 9767 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 9768 &incoming_new_repos_relpath, &incoming_new_pegrev, 9769 NULL, conflict, scratch_pool, 9770 scratch_pool)); 9771 9772 if (details->wc_move_targets) 9773 { 9774 apr_array_header_t *moves; 9775 9776 moves = svn_hash_gets(details->wc_move_targets, 9777 details->move_target_repos_relpath); 9778 merge_target_abspath = 9779 APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *); 9780 } 9781 else 9782 merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, 9783 details->preferred_sibling_idx, 9784 const char *); 9785 9786 /* ### The following WC modifications should be atomic. */ 9787 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 9788 &lock_abspath, ctx->wc_ctx, 9789 svn_dirent_get_longest_ancestor(conflict->local_abspath, 9790 merge_target_abspath, 9791 scratch_pool), 9792 scratch_pool, scratch_pool)); 9793 9794 /* Resolve to current working copy state. 9795 * svn_client__merge_locked() requires this. */ 9796 err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath, 9797 scratch_pool); 9798 if (err) 9799 goto unlock_wc; 9800 9801 /* Merge outstanding changes to the merge target. */ 9802 incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", 9803 incoming_old_repos_relpath, SVN_VA_NULL); 9804 incoming_old_opt_rev.kind = svn_opt_revision_number; 9805 incoming_old_opt_rev.value.number = incoming_old_pegrev; 9806 incoming_new_url = apr_pstrcat(scratch_pool, repos_root_url, "/", 9807 incoming_new_repos_relpath, SVN_VA_NULL); 9808 incoming_new_opt_rev.kind = svn_opt_revision_number; 9809 incoming_new_opt_rev.value.number = incoming_new_pegrev; 9810 err = svn_client__merge_locked(&conflict_report, 9811 incoming_old_url, &incoming_old_opt_rev, 9812 incoming_new_url, &incoming_new_opt_rev, 9813 merge_target_abspath, svn_depth_infinity, 9814 TRUE, TRUE, /* do a no-ancestry merge */ 9815 FALSE, FALSE, FALSE, 9816 TRUE, /* Allow mixed-rev just in case, 9817 * since conflict victims can't be 9818 * updated to straighten out 9819 * mixed-rev trees. */ 9820 NULL, ctx, scratch_pool, scratch_pool); 9821unlock_wc: 9822 svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); 9823 err = svn_error_compose_create(err, 9824 svn_wc__release_write_lock(ctx->wc_ctx, 9825 lock_abspath, 9826 scratch_pool)); 9827 if (err) 9828 return svn_error_trace(err); 9829 9830 if (ctx->notify_func2) 9831 { 9832 svn_wc_notify_t *notify; 9833 9834 /* Tell the world about the file merge that just happened. */ 9835 notify = svn_wc_create_notify(merge_target_abspath, 9836 svn_wc_notify_update_update, 9837 scratch_pool); 9838 if (conflict_report) 9839 notify->content_state = svn_wc_notify_state_conflicted; 9840 else 9841 notify->content_state = svn_wc_notify_state_merged; 9842 notify->kind = svn_node_dir; 9843 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 9844 9845 /* And also about the successfully resolved tree conflict. */ 9846 notify = svn_wc_create_notify(conflict->local_abspath, 9847 svn_wc_notify_resolved_tree, 9848 scratch_pool); 9849 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 9850 } 9851 9852 conflict->resolution_tree = svn_client_conflict_option_get_id(option); 9853 9854 return SVN_NO_ERROR; 9855} 9856 9857static svn_error_t * 9858assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) 9859{ 9860 svn_boolean_t text_conflicted; 9861 9862 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, NULL, NULL, 9863 conflict, scratch_pool, 9864 scratch_pool)); 9865 9866 SVN_ERR_ASSERT(text_conflicted); /* ### return proper error? */ 9867 9868 return SVN_NO_ERROR; 9869} 9870 9871static svn_error_t * 9872assert_prop_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) 9873{ 9874 apr_array_header_t *props_conflicted; 9875 9876 SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL, 9877 conflict, scratch_pool, 9878 scratch_pool)); 9879 9880 /* ### return proper error? */ 9881 SVN_ERR_ASSERT(props_conflicted && props_conflicted->nelts > 0); 9882 9883 return SVN_NO_ERROR; 9884} 9885 9886static svn_error_t * 9887assert_tree_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) 9888{ 9889 svn_boolean_t tree_conflicted; 9890 9891 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted, 9892 conflict, scratch_pool, 9893 scratch_pool)); 9894 9895 SVN_ERR_ASSERT(tree_conflicted); /* ### return proper error? */ 9896 9897 return SVN_NO_ERROR; 9898} 9899 9900/* Helper to add to conflict resolution option to array of OPTIONS. 9901 * Resolution option object will be allocated from OPTIONS->POOL 9902 * and DESCRIPTION will be copied to this pool. 9903 * Returns pointer to the created conflict resolution option. */ 9904static svn_client_conflict_option_t * 9905add_resolution_option(apr_array_header_t *options, 9906 svn_client_conflict_t *conflict, 9907 svn_client_conflict_option_id_t id, 9908 const char *label, 9909 const char *description, 9910 conflict_option_resolve_func_t resolve_func) 9911{ 9912 svn_client_conflict_option_t *option; 9913 9914 option = apr_pcalloc(options->pool, sizeof(*option)); 9915 option->pool = options->pool; 9916 option->id = id; 9917 option->label = apr_pstrdup(option->pool, label); 9918 option->description = apr_pstrdup(option->pool, description); 9919 option->conflict = conflict; 9920 option->do_resolve_func = resolve_func; 9921 9922 APR_ARRAY_PUSH(options, const svn_client_conflict_option_t *) = option; 9923 9924 return option; 9925} 9926 9927svn_error_t * 9928svn_client_conflict_text_get_resolution_options(apr_array_header_t **options, 9929 svn_client_conflict_t *conflict, 9930 svn_client_ctx_t *ctx, 9931 apr_pool_t *result_pool, 9932 apr_pool_t *scratch_pool) 9933{ 9934 const char *mime_type; 9935 9936 SVN_ERR(assert_text_conflict(conflict, scratch_pool)); 9937 9938 *options = apr_array_make(result_pool, 7, 9939 sizeof(svn_client_conflict_option_t *)); 9940 9941 add_resolution_option(*options, conflict, 9942 svn_client_conflict_option_postpone, 9943 _("Postpone"), 9944 _("skip this conflict and leave it unresolved"), 9945 resolve_postpone); 9946 9947 mime_type = svn_client_conflict_text_get_mime_type(conflict); 9948 if (mime_type && svn_mime_type_is_binary(mime_type)) 9949 { 9950 /* Resolver options for a binary file conflict. */ 9951 add_resolution_option(*options, conflict, 9952 svn_client_conflict_option_base_text, 9953 _("Accept base"), 9954 _("discard local and incoming changes for this binary file"), 9955 resolve_text_conflict); 9956 9957 add_resolution_option(*options, conflict, 9958 svn_client_conflict_option_incoming_text, 9959 _("Accept incoming"), 9960 _("accept incoming version of binary file"), 9961 resolve_text_conflict); 9962 9963 add_resolution_option(*options, conflict, 9964 svn_client_conflict_option_working_text, 9965 _("Mark as resolved"), 9966 _("accept binary file as it appears in the working copy"), 9967 resolve_text_conflict); 9968 } 9969 else 9970 { 9971 /* Resolver options for a text file conflict. */ 9972 add_resolution_option(*options, conflict, 9973 svn_client_conflict_option_base_text, 9974 _("Accept base"), 9975 _("discard local and incoming changes for this file"), 9976 resolve_text_conflict); 9977 9978 add_resolution_option(*options, conflict, 9979 svn_client_conflict_option_incoming_text, 9980 _("Accept incoming"), 9981 _("accept incoming version of entire file"), 9982 resolve_text_conflict); 9983 9984 add_resolution_option(*options, conflict, 9985 svn_client_conflict_option_working_text, 9986 _("Reject incoming"), 9987 _("reject all incoming changes for this file"), 9988 resolve_text_conflict); 9989 9990 add_resolution_option(*options, conflict, 9991 svn_client_conflict_option_incoming_text_where_conflicted, 9992 _("Accept incoming for conflicts"), 9993 _("accept incoming changes only where they conflict"), 9994 resolve_text_conflict); 9995 9996 add_resolution_option(*options, conflict, 9997 svn_client_conflict_option_working_text_where_conflicted, 9998 _("Reject conflicts"), 9999 _("reject incoming changes which conflict and accept the rest"), 10000 resolve_text_conflict); 10001 10002 add_resolution_option(*options, conflict, 10003 svn_client_conflict_option_merged_text, 10004 _("Mark as resolved"), 10005 _("accept the file as it appears in the working copy"), 10006 resolve_text_conflict); 10007 } 10008 10009 return SVN_NO_ERROR; 10010} 10011 10012svn_error_t * 10013svn_client_conflict_prop_get_resolution_options(apr_array_header_t **options, 10014 svn_client_conflict_t *conflict, 10015 svn_client_ctx_t *ctx, 10016 apr_pool_t *result_pool, 10017 apr_pool_t *scratch_pool) 10018{ 10019 SVN_ERR(assert_prop_conflict(conflict, scratch_pool)); 10020 10021 *options = apr_array_make(result_pool, 7, 10022 sizeof(svn_client_conflict_option_t *)); 10023 10024 add_resolution_option(*options, conflict, 10025 svn_client_conflict_option_postpone, 10026 _("Postpone"), 10027 _("skip this conflict and leave it unresolved"), 10028 resolve_postpone); 10029 10030 add_resolution_option(*options, conflict, 10031 svn_client_conflict_option_base_text, 10032 _("Accept base"), 10033 _("discard local and incoming changes for this property"), 10034 resolve_prop_conflict); 10035 10036 add_resolution_option(*options, conflict, 10037 svn_client_conflict_option_incoming_text, 10038 _("Accept incoming"), 10039 _("accept incoming version of entire property value"), 10040 resolve_prop_conflict); 10041 10042 add_resolution_option(*options, conflict, 10043 svn_client_conflict_option_working_text, 10044 _("Mark as resolved"), 10045 _("accept working copy version of entire property value"), 10046 resolve_prop_conflict); 10047 10048 add_resolution_option(*options, conflict, 10049 svn_client_conflict_option_incoming_text_where_conflicted, 10050 _("Accept incoming for conflicts"), 10051 _("accept incoming changes only where they conflict"), 10052 resolve_prop_conflict); 10053 10054 add_resolution_option(*options, conflict, 10055 svn_client_conflict_option_working_text_where_conflicted, 10056 _("Reject conflicts"), 10057 _("reject changes which conflict and accept the rest"), 10058 resolve_prop_conflict); 10059 10060 add_resolution_option(*options, conflict, 10061 svn_client_conflict_option_merged_text, 10062 _("Accept merged"), 10063 _("accept merged version of property value"), 10064 resolve_prop_conflict); 10065 10066 return SVN_NO_ERROR; 10067} 10068 10069/* Configure 'accept current wc state' resolution option for a tree conflict. */ 10070static svn_error_t * 10071configure_option_accept_current_wc_state(svn_client_conflict_t *conflict, 10072 apr_array_header_t *options) 10073{ 10074 svn_wc_operation_t operation; 10075 svn_wc_conflict_action_t incoming_change; 10076 svn_wc_conflict_reason_t local_change; 10077 conflict_option_resolve_func_t do_resolve_func; 10078 10079 operation = svn_client_conflict_get_operation(conflict); 10080 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10081 local_change = svn_client_conflict_get_local_change(conflict); 10082 10083 if ((operation == svn_wc_operation_update || 10084 operation == svn_wc_operation_switch) && 10085 (local_change == svn_wc_conflict_reason_moved_away || 10086 local_change == svn_wc_conflict_reason_deleted || 10087 local_change == svn_wc_conflict_reason_replaced) && 10088 incoming_change == svn_wc_conflict_action_edit) 10089 { 10090 /* We must break moves if the user accepts the current working copy 10091 * state instead of updating a moved-away node or updating children 10092 * moved outside of deleted or replaced directory nodes. 10093 * Else such moves would be left in an invalid state. */ 10094 do_resolve_func = resolve_update_break_moved_away; 10095 } 10096 else 10097 do_resolve_func = resolve_accept_current_wc_state; 10098 10099 add_resolution_option(options, conflict, 10100 svn_client_conflict_option_accept_current_wc_state, 10101 _("Mark as resolved"), 10102 _("accept current working copy state"), 10103 do_resolve_func); 10104 10105 return SVN_NO_ERROR; 10106} 10107 10108/* Configure 'update move destination' resolution option for a tree conflict. */ 10109static svn_error_t * 10110configure_option_update_move_destination(svn_client_conflict_t *conflict, 10111 apr_array_header_t *options) 10112{ 10113 svn_wc_operation_t operation; 10114 svn_wc_conflict_action_t incoming_change; 10115 svn_wc_conflict_reason_t local_change; 10116 10117 operation = svn_client_conflict_get_operation(conflict); 10118 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10119 local_change = svn_client_conflict_get_local_change(conflict); 10120 10121 if ((operation == svn_wc_operation_update || 10122 operation == svn_wc_operation_switch) && 10123 incoming_change == svn_wc_conflict_action_edit && 10124 local_change == svn_wc_conflict_reason_moved_away) 10125 { 10126 add_resolution_option( 10127 options, conflict, 10128 svn_client_conflict_option_update_move_destination, 10129 _("Update move destination"), 10130 _("apply incoming changes to move destination"), 10131 resolve_update_moved_away_node); 10132 } 10133 10134 return SVN_NO_ERROR; 10135} 10136 10137/* Configure 'update raise moved away children' resolution option for a tree 10138 * conflict. */ 10139static svn_error_t * 10140configure_option_update_raise_moved_away_children( 10141 svn_client_conflict_t *conflict, 10142 apr_array_header_t *options) 10143{ 10144 svn_wc_operation_t operation; 10145 svn_wc_conflict_action_t incoming_change; 10146 svn_wc_conflict_reason_t local_change; 10147 svn_node_kind_t victim_node_kind; 10148 10149 operation = svn_client_conflict_get_operation(conflict); 10150 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10151 local_change = svn_client_conflict_get_local_change(conflict); 10152 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10153 10154 if ((operation == svn_wc_operation_update || 10155 operation == svn_wc_operation_switch) && 10156 incoming_change == svn_wc_conflict_action_edit && 10157 (local_change == svn_wc_conflict_reason_deleted || 10158 local_change == svn_wc_conflict_reason_replaced) && 10159 victim_node_kind == svn_node_dir) 10160 { 10161 add_resolution_option( 10162 options, conflict, 10163 svn_client_conflict_option_update_any_moved_away_children, 10164 _("Update any moved-away children"), 10165 _("prepare for updating moved-away children, if any"), 10166 resolve_update_raise_moved_away); 10167 } 10168 10169 return SVN_NO_ERROR; 10170} 10171 10172/* Configure 'incoming add ignore' resolution option for a tree conflict. */ 10173static svn_error_t * 10174configure_option_incoming_add_ignore(svn_client_conflict_t *conflict, 10175 svn_client_ctx_t *ctx, 10176 apr_array_header_t *options, 10177 apr_pool_t *scratch_pool) 10178{ 10179 svn_wc_operation_t operation; 10180 svn_wc_conflict_action_t incoming_change; 10181 svn_wc_conflict_reason_t local_change; 10182 const char *incoming_new_repos_relpath; 10183 svn_revnum_t incoming_new_pegrev; 10184 svn_node_kind_t victim_node_kind; 10185 10186 operation = svn_client_conflict_get_operation(conflict); 10187 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10188 local_change = svn_client_conflict_get_local_change(conflict); 10189 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10190 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10191 &incoming_new_repos_relpath, &incoming_new_pegrev, 10192 NULL, conflict, scratch_pool, 10193 scratch_pool)); 10194 10195 /* This option is only available for directories. */ 10196 if (victim_node_kind == svn_node_dir && 10197 incoming_change == svn_wc_conflict_action_add && 10198 (local_change == svn_wc_conflict_reason_obstructed || 10199 local_change == svn_wc_conflict_reason_added)) 10200 { 10201 const char *description; 10202 const char *wcroot_abspath; 10203 10204 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 10205 conflict->local_abspath, scratch_pool, 10206 scratch_pool)); 10207 if (operation == svn_wc_operation_merge) 10208 description = 10209 apr_psprintf(scratch_pool, 10210 _("ignore and do not add '^/%s@%ld' here"), 10211 incoming_new_repos_relpath, incoming_new_pegrev); 10212 else if (operation == svn_wc_operation_update || 10213 operation == svn_wc_operation_switch) 10214 { 10215 if (victim_node_kind == svn_node_file) 10216 description = 10217 apr_psprintf(scratch_pool, 10218 _("replace '^/%s@%ld' with the locally added file"), 10219 incoming_new_repos_relpath, incoming_new_pegrev); 10220 else if (victim_node_kind == svn_node_dir) 10221 description = 10222 apr_psprintf(scratch_pool, 10223 _("replace '^/%s@%ld' with the locally added " 10224 "directory"), 10225 incoming_new_repos_relpath, incoming_new_pegrev); 10226 else 10227 description = 10228 apr_psprintf(scratch_pool, 10229 _("replace '^/%s@%ld' with the locally added item"), 10230 incoming_new_repos_relpath, incoming_new_pegrev); 10231 } 10232 else 10233 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 10234 _("unexpected operation code '%d'"), 10235 operation); 10236 add_resolution_option( 10237 options, conflict, svn_client_conflict_option_incoming_add_ignore, 10238 _("Ignore incoming addition"), description, resolve_incoming_add_ignore); 10239 } 10240 10241 return SVN_NO_ERROR; 10242} 10243 10244/* Configure 'incoming added file text merge' resolution option for a tree 10245 * conflict. */ 10246static svn_error_t * 10247configure_option_incoming_added_file_text_merge(svn_client_conflict_t *conflict, 10248 svn_client_ctx_t *ctx, 10249 apr_array_header_t *options, 10250 apr_pool_t *scratch_pool) 10251{ 10252 svn_wc_operation_t operation; 10253 svn_wc_conflict_action_t incoming_change; 10254 svn_wc_conflict_reason_t local_change; 10255 svn_node_kind_t victim_node_kind; 10256 const char *incoming_new_repos_relpath; 10257 svn_revnum_t incoming_new_pegrev; 10258 svn_node_kind_t incoming_new_kind; 10259 10260 operation = svn_client_conflict_get_operation(conflict); 10261 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10262 local_change = svn_client_conflict_get_local_change(conflict); 10263 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10264 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10265 &incoming_new_repos_relpath, &incoming_new_pegrev, 10266 &incoming_new_kind, conflict, scratch_pool, 10267 scratch_pool)); 10268 10269 if (victim_node_kind == svn_node_file && 10270 incoming_new_kind == svn_node_file && 10271 incoming_change == svn_wc_conflict_action_add && 10272 (local_change == svn_wc_conflict_reason_obstructed || 10273 local_change == svn_wc_conflict_reason_unversioned || 10274 local_change == svn_wc_conflict_reason_added)) 10275 { 10276 const char *description; 10277 const char *wcroot_abspath; 10278 10279 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 10280 conflict->local_abspath, scratch_pool, 10281 scratch_pool)); 10282 10283 if (operation == svn_wc_operation_merge) 10284 description = 10285 apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"), 10286 incoming_new_repos_relpath, incoming_new_pegrev, 10287 svn_dirent_local_style( 10288 svn_dirent_skip_ancestor(wcroot_abspath, 10289 conflict->local_abspath), 10290 scratch_pool)); 10291 else 10292 description = 10293 apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"), 10294 svn_dirent_local_style( 10295 svn_dirent_skip_ancestor(wcroot_abspath, 10296 conflict->local_abspath), 10297 scratch_pool), 10298 incoming_new_repos_relpath, incoming_new_pegrev); 10299 10300 add_resolution_option( 10301 options, conflict, 10302 svn_client_conflict_option_incoming_added_file_text_merge, 10303 _("Merge the files"), description, 10304 operation == svn_wc_operation_merge 10305 ? resolve_merge_incoming_added_file_text_merge 10306 : resolve_merge_incoming_added_file_text_update); 10307 } 10308 10309 return SVN_NO_ERROR; 10310} 10311 10312/* Configure 'incoming added file replace and merge' resolution option for a 10313 * tree conflict. */ 10314static svn_error_t * 10315configure_option_incoming_added_file_replace_and_merge( 10316 svn_client_conflict_t *conflict, 10317 svn_client_ctx_t *ctx, 10318 apr_array_header_t *options, 10319 apr_pool_t *scratch_pool) 10320{ 10321 svn_wc_operation_t operation; 10322 svn_wc_conflict_action_t incoming_change; 10323 svn_wc_conflict_reason_t local_change; 10324 svn_node_kind_t victim_node_kind; 10325 const char *incoming_new_repos_relpath; 10326 svn_revnum_t incoming_new_pegrev; 10327 svn_node_kind_t incoming_new_kind; 10328 10329 operation = svn_client_conflict_get_operation(conflict); 10330 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10331 local_change = svn_client_conflict_get_local_change(conflict); 10332 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10333 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10334 &incoming_new_repos_relpath, &incoming_new_pegrev, 10335 &incoming_new_kind, conflict, scratch_pool, 10336 scratch_pool)); 10337 10338 if (operation == svn_wc_operation_merge && 10339 victim_node_kind == svn_node_file && 10340 incoming_new_kind == svn_node_file && 10341 incoming_change == svn_wc_conflict_action_add && 10342 local_change == svn_wc_conflict_reason_obstructed) 10343 { 10344 const char *wcroot_abspath; 10345 const char *description; 10346 10347 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 10348 conflict->local_abspath, scratch_pool, 10349 scratch_pool)); 10350 description = 10351 apr_psprintf(scratch_pool, 10352 _("delete '%s', copy '^/%s@%ld' here, and merge the files"), 10353 svn_dirent_local_style( 10354 svn_dirent_skip_ancestor(wcroot_abspath, 10355 conflict->local_abspath), 10356 scratch_pool), 10357 incoming_new_repos_relpath, incoming_new_pegrev); 10358 10359 add_resolution_option( 10360 options, conflict, 10361 svn_client_conflict_option_incoming_added_file_replace_and_merge, 10362 _("Replace and merge"), 10363 description, resolve_merge_incoming_added_file_replace_and_merge); 10364 } 10365 10366 return SVN_NO_ERROR; 10367} 10368 10369/* Configure 'incoming added dir merge' resolution option for a tree 10370 * conflict. */ 10371static svn_error_t * 10372configure_option_incoming_added_dir_merge(svn_client_conflict_t *conflict, 10373 svn_client_ctx_t *ctx, 10374 apr_array_header_t *options, 10375 apr_pool_t *scratch_pool) 10376{ 10377 svn_wc_operation_t operation; 10378 svn_wc_conflict_action_t incoming_change; 10379 svn_wc_conflict_reason_t local_change; 10380 svn_node_kind_t victim_node_kind; 10381 const char *incoming_new_repos_relpath; 10382 svn_revnum_t incoming_new_pegrev; 10383 svn_node_kind_t incoming_new_kind; 10384 10385 operation = svn_client_conflict_get_operation(conflict); 10386 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10387 local_change = svn_client_conflict_get_local_change(conflict); 10388 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10389 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10390 &incoming_new_repos_relpath, &incoming_new_pegrev, 10391 &incoming_new_kind, conflict, scratch_pool, 10392 scratch_pool)); 10393 10394 if (victim_node_kind == svn_node_dir && 10395 incoming_new_kind == svn_node_dir && 10396 incoming_change == svn_wc_conflict_action_add && 10397 (local_change == svn_wc_conflict_reason_added || 10398 (operation == svn_wc_operation_merge && 10399 local_change == svn_wc_conflict_reason_obstructed) || 10400 (operation != svn_wc_operation_merge && 10401 local_change == svn_wc_conflict_reason_unversioned))) 10402 { 10403 const char *description; 10404 const char *wcroot_abspath; 10405 10406 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 10407 conflict->local_abspath, scratch_pool, 10408 scratch_pool)); 10409 if (operation == svn_wc_operation_merge) 10410 { 10411 if (conflict->tree_conflict_incoming_details == NULL) 10412 return SVN_NO_ERROR; 10413 10414 description = 10415 apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"), 10416 incoming_new_repos_relpath, incoming_new_pegrev, 10417 svn_dirent_local_style( 10418 svn_dirent_skip_ancestor(wcroot_abspath, 10419 conflict->local_abspath), 10420 scratch_pool)); 10421 } 10422 else 10423 description = 10424 apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"), 10425 svn_dirent_local_style( 10426 svn_dirent_skip_ancestor(wcroot_abspath, 10427 conflict->local_abspath), 10428 scratch_pool), 10429 incoming_new_repos_relpath, incoming_new_pegrev); 10430 10431 add_resolution_option(options, conflict, 10432 svn_client_conflict_option_incoming_added_dir_merge, 10433 _("Merge the directories"), description, 10434 operation == svn_wc_operation_merge 10435 ? resolve_merge_incoming_added_dir_merge 10436 : resolve_update_incoming_added_dir_merge); 10437 } 10438 10439 return SVN_NO_ERROR; 10440} 10441 10442/* Configure 'incoming added dir replace' resolution option for a tree 10443 * conflict. */ 10444static svn_error_t * 10445configure_option_incoming_added_dir_replace(svn_client_conflict_t *conflict, 10446 svn_client_ctx_t *ctx, 10447 apr_array_header_t *options, 10448 apr_pool_t *scratch_pool) 10449{ 10450 svn_wc_operation_t operation; 10451 svn_wc_conflict_action_t incoming_change; 10452 svn_wc_conflict_reason_t local_change; 10453 svn_node_kind_t victim_node_kind; 10454 const char *incoming_new_repos_relpath; 10455 svn_revnum_t incoming_new_pegrev; 10456 svn_node_kind_t incoming_new_kind; 10457 10458 operation = svn_client_conflict_get_operation(conflict); 10459 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10460 local_change = svn_client_conflict_get_local_change(conflict); 10461 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10462 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10463 &incoming_new_repos_relpath, &incoming_new_pegrev, 10464 &incoming_new_kind, conflict, scratch_pool, 10465 scratch_pool)); 10466 10467 if (operation == svn_wc_operation_merge && 10468 victim_node_kind == svn_node_dir && 10469 incoming_new_kind == svn_node_dir && 10470 incoming_change == svn_wc_conflict_action_add && 10471 local_change == svn_wc_conflict_reason_obstructed) 10472 { 10473 const char *description; 10474 const char *wcroot_abspath; 10475 10476 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 10477 conflict->local_abspath, scratch_pool, 10478 scratch_pool)); 10479 description = 10480 apr_psprintf(scratch_pool, _("delete '%s' and copy '^/%s@%ld' here"), 10481 svn_dirent_local_style( 10482 svn_dirent_skip_ancestor(wcroot_abspath, 10483 conflict->local_abspath), 10484 scratch_pool), 10485 incoming_new_repos_relpath, incoming_new_pegrev); 10486 add_resolution_option( 10487 options, conflict, 10488 svn_client_conflict_option_incoming_added_dir_replace, 10489 _("Delete my directory and replace it with incoming directory"), 10490 description, resolve_merge_incoming_added_dir_replace); 10491 } 10492 10493 return SVN_NO_ERROR; 10494} 10495 10496/* Configure 'incoming added dir replace and merge' resolution option 10497 * for a tree conflict. */ 10498static svn_error_t * 10499configure_option_incoming_added_dir_replace_and_merge( 10500 svn_client_conflict_t *conflict, 10501 svn_client_ctx_t *ctx, 10502 apr_array_header_t *options, 10503 apr_pool_t *scratch_pool) 10504{ 10505 svn_wc_operation_t operation; 10506 svn_wc_conflict_action_t incoming_change; 10507 svn_wc_conflict_reason_t local_change; 10508 svn_node_kind_t victim_node_kind; 10509 const char *incoming_new_repos_relpath; 10510 svn_revnum_t incoming_new_pegrev; 10511 svn_node_kind_t incoming_new_kind; 10512 10513 operation = svn_client_conflict_get_operation(conflict); 10514 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10515 local_change = svn_client_conflict_get_local_change(conflict); 10516 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10517 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10518 &incoming_new_repos_relpath, &incoming_new_pegrev, 10519 &incoming_new_kind, conflict, scratch_pool, 10520 scratch_pool)); 10521 10522 if (operation == svn_wc_operation_merge && 10523 victim_node_kind == svn_node_dir && 10524 incoming_new_kind == svn_node_dir && 10525 incoming_change == svn_wc_conflict_action_add && 10526 local_change == svn_wc_conflict_reason_obstructed) 10527 { 10528 const char *description; 10529 const char *wcroot_abspath; 10530 10531 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 10532 conflict->local_abspath, scratch_pool, 10533 scratch_pool)); 10534 description = 10535 apr_psprintf(scratch_pool, 10536 _("delete '%s', copy '^/%s@%ld' here, and merge the directories"), 10537 svn_dirent_local_style( 10538 svn_dirent_skip_ancestor(wcroot_abspath, 10539 conflict->local_abspath), 10540 scratch_pool), 10541 incoming_new_repos_relpath, incoming_new_pegrev); 10542 10543 add_resolution_option( 10544 options, conflict, 10545 svn_client_conflict_option_incoming_added_dir_replace_and_merge, 10546 _("Replace and merge"), 10547 description, resolve_merge_incoming_added_dir_replace_and_merge); 10548 } 10549 10550 return SVN_NO_ERROR; 10551} 10552 10553/* Configure 'incoming delete ignore' resolution option for a tree conflict. */ 10554static svn_error_t * 10555configure_option_incoming_delete_ignore(svn_client_conflict_t *conflict, 10556 svn_client_ctx_t *ctx, 10557 apr_array_header_t *options, 10558 apr_pool_t *scratch_pool) 10559{ 10560 svn_wc_operation_t operation; 10561 svn_wc_conflict_action_t incoming_change; 10562 svn_wc_conflict_reason_t local_change; 10563 const char *incoming_new_repos_relpath; 10564 svn_revnum_t incoming_new_pegrev; 10565 10566 operation = svn_client_conflict_get_operation(conflict); 10567 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10568 local_change = svn_client_conflict_get_local_change(conflict); 10569 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10570 &incoming_new_repos_relpath, &incoming_new_pegrev, 10571 NULL, conflict, scratch_pool, 10572 scratch_pool)); 10573 10574 if (incoming_change == svn_wc_conflict_action_delete) 10575 { 10576 const char *description; 10577 struct conflict_tree_incoming_delete_details *incoming_details; 10578 svn_boolean_t is_incoming_move; 10579 10580 incoming_details = conflict->tree_conflict_incoming_details; 10581 is_incoming_move = (incoming_details != NULL && 10582 incoming_details->moves != NULL); 10583 if (local_change == svn_wc_conflict_reason_moved_away || 10584 local_change == svn_wc_conflict_reason_edited) 10585 { 10586 /* An option which ignores the incoming deletion makes no sense 10587 * if we know there was a local move and/or an incoming move. */ 10588 if (is_incoming_move) 10589 return SVN_NO_ERROR; 10590 } 10591 else if (local_change == svn_wc_conflict_reason_deleted) 10592 { 10593 /* If the local item was deleted and conflict details were fetched 10594 * and indicate that there was no move, then this is an actual 10595 * 'delete vs delete' situation. An option which ignores the incoming 10596 * deletion makes no sense in that case because there is no local 10597 * node to preserve. */ 10598 if (!is_incoming_move) 10599 return SVN_NO_ERROR; 10600 } 10601 else if (local_change == svn_wc_conflict_reason_missing && 10602 operation == svn_wc_operation_merge) 10603 { 10604 struct conflict_tree_local_missing_details *local_details; 10605 svn_boolean_t is_local_move; /* "local" to branch history */ 10606 10607 local_details = conflict->tree_conflict_local_details; 10608 is_local_move = (local_details != NULL && 10609 local_details->moves != NULL); 10610 10611 if (is_incoming_move || is_local_move) 10612 return SVN_NO_ERROR; 10613 } 10614 10615 description = 10616 apr_psprintf(scratch_pool, _("ignore the deletion of '^/%s@%ld'"), 10617 incoming_new_repos_relpath, incoming_new_pegrev); 10618 10619 add_resolution_option(options, conflict, 10620 svn_client_conflict_option_incoming_delete_ignore, 10621 _("Ignore incoming deletion"), description, 10622 resolve_incoming_delete_ignore); 10623 } 10624 10625 return SVN_NO_ERROR; 10626} 10627 10628/* Configure 'incoming delete accept' resolution option for a tree conflict. */ 10629static svn_error_t * 10630configure_option_incoming_delete_accept(svn_client_conflict_t *conflict, 10631 svn_client_ctx_t *ctx, 10632 apr_array_header_t *options, 10633 apr_pool_t *scratch_pool) 10634{ 10635 svn_wc_conflict_action_t incoming_change; 10636 svn_wc_conflict_reason_t local_change; 10637 const char *incoming_new_repos_relpath; 10638 svn_revnum_t incoming_new_pegrev; 10639 10640 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10641 local_change = svn_client_conflict_get_local_change(conflict); 10642 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10643 &incoming_new_repos_relpath, &incoming_new_pegrev, 10644 NULL, conflict, scratch_pool, 10645 scratch_pool)); 10646 10647 if (incoming_change == svn_wc_conflict_action_delete) 10648 { 10649 struct conflict_tree_incoming_delete_details *incoming_details; 10650 svn_boolean_t is_incoming_move; 10651 10652 incoming_details = conflict->tree_conflict_incoming_details; 10653 is_incoming_move = (incoming_details != NULL && 10654 incoming_details->moves != NULL); 10655 if (is_incoming_move && 10656 (local_change == svn_wc_conflict_reason_edited || 10657 local_change == svn_wc_conflict_reason_moved_away || 10658 local_change == svn_wc_conflict_reason_missing)) 10659 { 10660 /* An option which accepts the incoming deletion makes no sense 10661 * if we know there was a local move and/or an incoming move. */ 10662 return SVN_NO_ERROR; 10663 } 10664 else 10665 { 10666 const char *description; 10667 const char *wcroot_abspath; 10668 const char *local_abspath; 10669 10670 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 10671 conflict->local_abspath, scratch_pool, 10672 scratch_pool)); 10673 local_abspath = svn_client_conflict_get_local_abspath(conflict); 10674 description = 10675 apr_psprintf(scratch_pool, _("accept the deletion of '%s'"), 10676 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, 10677 local_abspath), 10678 scratch_pool)); 10679 add_resolution_option( 10680 options, conflict, 10681 svn_client_conflict_option_incoming_delete_accept, 10682 _("Accept incoming deletion"), description, 10683 resolve_incoming_delete_accept); 10684 } 10685 } 10686 10687 return SVN_NO_ERROR; 10688} 10689 10690static svn_error_t * 10691describe_incoming_move_merge_conflict_option( 10692 const char **description, 10693 svn_client_conflict_t *conflict, 10694 svn_client_ctx_t *ctx, 10695 const char *moved_to_abspath, 10696 apr_pool_t *result_pool, 10697 apr_pool_t *scratch_pool) 10698{ 10699 svn_wc_operation_t operation; 10700 const char *victim_abspath; 10701 svn_node_kind_t victim_node_kind; 10702 const char *wcroot_abspath; 10703 10704 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 10705 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10706 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 10707 victim_abspath, scratch_pool, 10708 scratch_pool)); 10709 10710 operation = svn_client_conflict_get_operation(conflict); 10711 if (operation == svn_wc_operation_merge) 10712 { 10713 const char *incoming_moved_abspath = NULL; 10714 10715 if (victim_node_kind == svn_node_none) 10716 { 10717 /* This is an incoming move vs local move conflict. */ 10718 struct conflict_tree_incoming_delete_details *details; 10719 10720 details = conflict->tree_conflict_incoming_details; 10721 if (details->wc_move_targets) 10722 { 10723 apr_array_header_t *moves; 10724 10725 moves = svn_hash_gets(details->wc_move_targets, 10726 details->move_target_repos_relpath); 10727 incoming_moved_abspath = 10728 APR_ARRAY_IDX(moves, details->wc_move_target_idx, 10729 const char *); 10730 } 10731 } 10732 10733 if (incoming_moved_abspath) 10734 { 10735 /* The 'move and merge' option follows the incoming move; note that 10736 * moved_to_abspath points to the current location of an item which 10737 * was moved in the history of our merge target branch. If the user 10738 * chooses 'move and merge', that item will be moved again (i.e. it 10739 * will be moved to and merged with incoming_moved_abspath's item). */ 10740 *description = 10741 apr_psprintf( 10742 result_pool, _("move '%s' to '%s' and merge"), 10743 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, 10744 moved_to_abspath), 10745 scratch_pool), 10746 svn_dirent_local_style(svn_dirent_skip_ancestor( 10747 wcroot_abspath, 10748 incoming_moved_abspath), 10749 scratch_pool)); 10750 } 10751 else 10752 { 10753 *description = 10754 apr_psprintf( 10755 result_pool, _("move '%s' to '%s' and merge"), 10756 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, 10757 victim_abspath), 10758 scratch_pool), 10759 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, 10760 moved_to_abspath), 10761 scratch_pool)); 10762 } 10763 } 10764 else 10765 *description = 10766 apr_psprintf( 10767 result_pool, _("move and merge local changes from '%s' into '%s'"), 10768 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, 10769 victim_abspath), 10770 scratch_pool), 10771 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, 10772 moved_to_abspath), 10773 scratch_pool)); 10774 10775 return SVN_NO_ERROR; 10776} 10777 10778/* Configure 'incoming move file merge' resolution option for 10779 * a tree conflict. */ 10780static svn_error_t * 10781configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict, 10782 svn_client_ctx_t *ctx, 10783 apr_array_header_t *options, 10784 apr_pool_t *scratch_pool) 10785{ 10786 svn_node_kind_t victim_node_kind; 10787 svn_wc_conflict_action_t incoming_change; 10788 svn_wc_conflict_reason_t local_change; 10789 const char *incoming_old_repos_relpath; 10790 svn_revnum_t incoming_old_pegrev; 10791 svn_node_kind_t incoming_old_kind; 10792 const char *incoming_new_repos_relpath; 10793 svn_revnum_t incoming_new_pegrev; 10794 svn_node_kind_t incoming_new_kind; 10795 10796 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10797 local_change = svn_client_conflict_get_local_change(conflict); 10798 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10799 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 10800 &incoming_old_repos_relpath, &incoming_old_pegrev, 10801 &incoming_old_kind, conflict, scratch_pool, 10802 scratch_pool)); 10803 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10804 &incoming_new_repos_relpath, &incoming_new_pegrev, 10805 &incoming_new_kind, conflict, scratch_pool, 10806 scratch_pool)); 10807 10808 if (victim_node_kind == svn_node_file && 10809 incoming_old_kind == svn_node_file && 10810 incoming_new_kind == svn_node_none && 10811 incoming_change == svn_wc_conflict_action_delete && 10812 local_change == svn_wc_conflict_reason_edited) 10813 { 10814 struct conflict_tree_incoming_delete_details *details; 10815 const char *description; 10816 apr_array_header_t *move_target_wc_abspaths; 10817 const char *moved_to_abspath; 10818 10819 details = conflict->tree_conflict_incoming_details; 10820 if (details == NULL || details->moves == NULL) 10821 return SVN_NO_ERROR; 10822 10823 if (apr_hash_count(details->wc_move_targets) == 0) 10824 return SVN_NO_ERROR; 10825 10826 move_target_wc_abspaths = 10827 svn_hash_gets(details->wc_move_targets, 10828 get_moved_to_repos_relpath(details, scratch_pool)); 10829 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, 10830 details->wc_move_target_idx, 10831 const char *); 10832 SVN_ERR(describe_incoming_move_merge_conflict_option(&description, 10833 conflict, ctx, 10834 moved_to_abspath, 10835 scratch_pool, 10836 scratch_pool)); 10837 add_resolution_option( 10838 options, conflict, 10839 svn_client_conflict_option_incoming_move_file_text_merge, 10840 _("Move and merge"), description, 10841 resolve_incoming_move_file_text_merge); 10842 } 10843 10844 return SVN_NO_ERROR; 10845} 10846 10847/* Configure 'incoming move dir merge' resolution option for 10848 * a tree conflict. */ 10849static svn_error_t * 10850configure_option_incoming_dir_merge(svn_client_conflict_t *conflict, 10851 svn_client_ctx_t *ctx, 10852 apr_array_header_t *options, 10853 apr_pool_t *scratch_pool) 10854{ 10855 svn_node_kind_t victim_node_kind; 10856 svn_wc_conflict_action_t incoming_change; 10857 svn_wc_conflict_reason_t local_change; 10858 const char *incoming_old_repos_relpath; 10859 svn_revnum_t incoming_old_pegrev; 10860 svn_node_kind_t incoming_old_kind; 10861 const char *incoming_new_repos_relpath; 10862 svn_revnum_t incoming_new_pegrev; 10863 svn_node_kind_t incoming_new_kind; 10864 10865 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10866 local_change = svn_client_conflict_get_local_change(conflict); 10867 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 10868 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 10869 &incoming_old_repos_relpath, &incoming_old_pegrev, 10870 &incoming_old_kind, conflict, scratch_pool, 10871 scratch_pool)); 10872 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10873 &incoming_new_repos_relpath, &incoming_new_pegrev, 10874 &incoming_new_kind, conflict, scratch_pool, 10875 scratch_pool)); 10876 10877 if (victim_node_kind == svn_node_dir && 10878 incoming_old_kind == svn_node_dir && 10879 incoming_new_kind == svn_node_none && 10880 incoming_change == svn_wc_conflict_action_delete && 10881 local_change == svn_wc_conflict_reason_edited) 10882 { 10883 struct conflict_tree_incoming_delete_details *details; 10884 const char *description; 10885 apr_array_header_t *move_target_wc_abspaths; 10886 const char *moved_to_abspath; 10887 10888 details = conflict->tree_conflict_incoming_details; 10889 if (details == NULL || details->moves == NULL) 10890 return SVN_NO_ERROR; 10891 10892 if (apr_hash_count(details->wc_move_targets) == 0) 10893 return SVN_NO_ERROR; 10894 10895 move_target_wc_abspaths = 10896 svn_hash_gets(details->wc_move_targets, 10897 get_moved_to_repos_relpath(details, scratch_pool)); 10898 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, 10899 details->wc_move_target_idx, 10900 const char *); 10901 SVN_ERR(describe_incoming_move_merge_conflict_option(&description, 10902 conflict, ctx, 10903 moved_to_abspath, 10904 scratch_pool, 10905 scratch_pool)); 10906 add_resolution_option(options, conflict, 10907 svn_client_conflict_option_incoming_move_dir_merge, 10908 _("Move and merge"), description, 10909 resolve_incoming_move_dir_merge); 10910 } 10911 10912 return SVN_NO_ERROR; 10913} 10914 10915/* Configure 'local move file merge' resolution option for 10916 * a tree conflict. */ 10917static svn_error_t * 10918configure_option_local_move_file_or_dir_merge( 10919 svn_client_conflict_t *conflict, 10920 svn_client_ctx_t *ctx, 10921 apr_array_header_t *options, 10922 apr_pool_t *scratch_pool) 10923{ 10924 svn_wc_operation_t operation; 10925 svn_wc_conflict_action_t incoming_change; 10926 svn_wc_conflict_reason_t local_change; 10927 const char *incoming_old_repos_relpath; 10928 svn_revnum_t incoming_old_pegrev; 10929 svn_node_kind_t incoming_old_kind; 10930 const char *incoming_new_repos_relpath; 10931 svn_revnum_t incoming_new_pegrev; 10932 svn_node_kind_t incoming_new_kind; 10933 10934 operation = svn_client_conflict_get_operation(conflict); 10935 incoming_change = svn_client_conflict_get_incoming_change(conflict); 10936 local_change = svn_client_conflict_get_local_change(conflict); 10937 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 10938 &incoming_old_repos_relpath, &incoming_old_pegrev, 10939 &incoming_old_kind, conflict, scratch_pool, 10940 scratch_pool)); 10941 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 10942 &incoming_new_repos_relpath, &incoming_new_pegrev, 10943 &incoming_new_kind, conflict, scratch_pool, 10944 scratch_pool)); 10945 10946 if (operation == svn_wc_operation_merge && 10947 incoming_change == svn_wc_conflict_action_edit && 10948 local_change == svn_wc_conflict_reason_missing) 10949 { 10950 struct conflict_tree_local_missing_details *details; 10951 const char *wcroot_abspath; 10952 10953 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 10954 conflict->local_abspath, 10955 scratch_pool, scratch_pool)); 10956 10957 details = conflict->tree_conflict_local_details; 10958 if (details != NULL && details->moves != NULL && 10959 details->move_target_repos_relpath != NULL) 10960 { 10961 apr_array_header_t *moves; 10962 const char *moved_to_abspath; 10963 const char *description; 10964 10965 moves = svn_hash_gets(details->wc_move_targets, 10966 details->move_target_repos_relpath); 10967 moved_to_abspath = 10968 APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *); 10969 10970 description = 10971 apr_psprintf( 10972 scratch_pool, _("apply changes to move destination '%s'"), 10973 svn_dirent_local_style( 10974 svn_dirent_skip_ancestor(wcroot_abspath, moved_to_abspath), 10975 scratch_pool)); 10976 10977 if ((incoming_old_kind == svn_node_file || 10978 incoming_old_kind == svn_node_none) && 10979 (incoming_new_kind == svn_node_file || 10980 incoming_new_kind == svn_node_none)) 10981 { 10982 add_resolution_option( 10983 options, conflict, 10984 svn_client_conflict_option_local_move_file_text_merge, 10985 _("Apply to move destination"), 10986 description, resolve_local_move_file_merge); 10987 } 10988 else 10989 { 10990 add_resolution_option( 10991 options, conflict, 10992 svn_client_conflict_option_local_move_dir_merge, 10993 _("Apply to move destination"), 10994 description, resolve_local_move_dir_merge); 10995 } 10996 } 10997 } 10998 10999 return SVN_NO_ERROR; 11000} 11001 11002/* Configure 'sibling move file/dir merge' resolution option for 11003 * a tree conflict. */ 11004static svn_error_t * 11005configure_option_sibling_move_merge(svn_client_conflict_t *conflict, 11006 svn_client_ctx_t *ctx, 11007 apr_array_header_t *options, 11008 apr_pool_t *scratch_pool) 11009{ 11010 svn_wc_operation_t operation; 11011 svn_wc_conflict_action_t incoming_change; 11012 svn_wc_conflict_reason_t local_change; 11013 const char *incoming_old_repos_relpath; 11014 svn_revnum_t incoming_old_pegrev; 11015 svn_node_kind_t incoming_old_kind; 11016 const char *incoming_new_repos_relpath; 11017 svn_revnum_t incoming_new_pegrev; 11018 svn_node_kind_t incoming_new_kind; 11019 11020 operation = svn_client_conflict_get_operation(conflict); 11021 incoming_change = svn_client_conflict_get_incoming_change(conflict); 11022 local_change = svn_client_conflict_get_local_change(conflict); 11023 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 11024 &incoming_old_repos_relpath, &incoming_old_pegrev, 11025 &incoming_old_kind, conflict, scratch_pool, 11026 scratch_pool)); 11027 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 11028 &incoming_new_repos_relpath, &incoming_new_pegrev, 11029 &incoming_new_kind, conflict, scratch_pool, 11030 scratch_pool)); 11031 11032 if (operation == svn_wc_operation_merge && 11033 incoming_change == svn_wc_conflict_action_edit && 11034 local_change == svn_wc_conflict_reason_missing) 11035 { 11036 struct conflict_tree_local_missing_details *details; 11037 const char *wcroot_abspath; 11038 11039 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 11040 conflict->local_abspath, 11041 scratch_pool, scratch_pool)); 11042 11043 details = conflict->tree_conflict_local_details; 11044 if (details != NULL && details->wc_siblings != NULL) 11045 { 11046 const char *description; 11047 const char *sibling; 11048 11049 sibling = 11050 apr_pstrdup(conflict->pool, 11051 APR_ARRAY_IDX(details->wc_siblings, 11052 details->preferred_sibling_idx, 11053 const char *)); 11054 description = 11055 apr_psprintf( 11056 scratch_pool, _("apply changes to '%s'"), 11057 svn_dirent_local_style( 11058 svn_dirent_skip_ancestor(wcroot_abspath, sibling), 11059 scratch_pool)); 11060 11061 if ((incoming_old_kind == svn_node_file || 11062 incoming_old_kind == svn_node_none) && 11063 (incoming_new_kind == svn_node_file || 11064 incoming_new_kind == svn_node_none)) 11065 { 11066 add_resolution_option( 11067 options, conflict, 11068 svn_client_conflict_option_sibling_move_file_text_merge, 11069 _("Apply to corresponding local location"), 11070 description, resolve_local_move_file_merge); 11071 } 11072 else 11073 { 11074 add_resolution_option( 11075 options, conflict, 11076 svn_client_conflict_option_sibling_move_dir_merge, 11077 _("Apply to corresponding local location"), 11078 description, resolve_local_move_dir_merge); 11079 } 11080 } 11081 } 11082 11083 return SVN_NO_ERROR; 11084} 11085 11086struct conflict_tree_update_local_moved_away_details { 11087 /* 11088 * This array consists of "const char *" absolute paths to working copy 11089 * nodes which are uncomitted copies and correspond to the repository path 11090 * of the conflict victim. 11091 * Each such working copy node is a potential local move target which can 11092 * be chosen to find a suitable merge target when resolving a tree conflict. 11093 * 11094 * This may be an empty array in case if there is no move target path in 11095 * the working copy. */ 11096 apr_array_header_t *wc_move_targets; 11097 11098 /* Current index into the list of working copy paths in WC_MOVE_TARGETS. */ 11099 int preferred_move_target_idx; 11100}; 11101 11102/* Implements conflict_option_resolve_func_t. 11103 * Resolve an incoming move vs local move conflict by merging from the 11104 * incoming move's target location to the local move's target location, 11105 * overriding the incoming move. The original local move was broken during 11106 * update/switch, so overriding the incoming move involves recording a new 11107 * move from the incoming move's target location to the local move's target 11108 * location. */ 11109static svn_error_t * 11110resolve_both_moved_file_update_keep_local_move( 11111 svn_client_conflict_option_t *option, 11112 svn_client_conflict_t *conflict, 11113 svn_client_ctx_t *ctx, 11114 apr_pool_t *scratch_pool) 11115{ 11116 svn_client_conflict_option_id_t option_id; 11117 const char *victim_abspath; 11118 const char *local_moved_to_abspath; 11119 svn_wc_operation_t operation; 11120 const char *lock_abspath; 11121 svn_error_t *err; 11122 const char *repos_root_url; 11123 const char *incoming_old_repos_relpath; 11124 svn_revnum_t incoming_old_pegrev; 11125 const char *incoming_new_repos_relpath; 11126 svn_revnum_t incoming_new_pegrev; 11127 const char *wc_tmpdir; 11128 const char *ancestor_abspath; 11129 svn_stream_t *ancestor_stream; 11130 apr_hash_t *ancestor_props; 11131 apr_hash_t *incoming_props; 11132 apr_hash_t *local_props; 11133 const char *ancestor_url; 11134 const char *corrected_url; 11135 svn_ra_session_t *ra_session; 11136 svn_wc_merge_outcome_t merge_content_outcome; 11137 svn_wc_notify_state_t merge_props_outcome; 11138 apr_array_header_t *propdiffs; 11139 struct conflict_tree_incoming_delete_details *incoming_details; 11140 apr_array_header_t *possible_moved_to_abspaths; 11141 const char *incoming_moved_to_abspath; 11142 struct conflict_tree_update_local_moved_away_details *local_details; 11143 11144 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 11145 operation = svn_client_conflict_get_operation(conflict); 11146 incoming_details = conflict->tree_conflict_incoming_details; 11147 if (incoming_details == NULL || incoming_details->moves == NULL) 11148 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 11149 _("The specified conflict resolution option " 11150 "requires details for tree conflict at '%s' " 11151 "to be fetched from the repository first."), 11152 svn_dirent_local_style(victim_abspath, 11153 scratch_pool)); 11154 if (operation == svn_wc_operation_none) 11155 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 11156 _("Invalid operation code '%d' recorded for " 11157 "conflict at '%s'"), operation, 11158 svn_dirent_local_style(victim_abspath, 11159 scratch_pool)); 11160 11161 option_id = svn_client_conflict_option_get_id(option); 11162 SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge); 11163 11164 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 11165 conflict, scratch_pool, 11166 scratch_pool)); 11167 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 11168 &incoming_old_repos_relpath, &incoming_old_pegrev, 11169 NULL, conflict, scratch_pool, 11170 scratch_pool)); 11171 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 11172 &incoming_new_repos_relpath, &incoming_new_pegrev, 11173 NULL, conflict, scratch_pool, 11174 scratch_pool)); 11175 11176 /* Set up temporary storage for the common ancestor version of the file. */ 11177 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, 11178 scratch_pool, scratch_pool)); 11179 SVN_ERR(svn_stream_open_unique(&ancestor_stream, 11180 &ancestor_abspath, wc_tmpdir, 11181 svn_io_file_del_on_pool_cleanup, 11182 scratch_pool, scratch_pool)); 11183 11184 /* Fetch the ancestor file's content. */ 11185 ancestor_url = svn_path_url_add_component2(repos_root_url, 11186 incoming_old_repos_relpath, 11187 scratch_pool); 11188 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 11189 ancestor_url, NULL, NULL, 11190 FALSE, FALSE, ctx, 11191 scratch_pool, scratch_pool)); 11192 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, 11193 ancestor_stream, NULL, /* fetched_rev */ 11194 &ancestor_props, scratch_pool)); 11195 filter_props(ancestor_props, scratch_pool); 11196 11197 /* Close stream to flush ancestor file to disk. */ 11198 SVN_ERR(svn_stream_close(ancestor_stream)); 11199 11200 possible_moved_to_abspaths = 11201 svn_hash_gets(incoming_details->wc_move_targets, 11202 get_moved_to_repos_relpath(incoming_details, scratch_pool)); 11203 incoming_moved_to_abspath = 11204 APR_ARRAY_IDX(possible_moved_to_abspaths, 11205 incoming_details->wc_move_target_idx, const char *); 11206 11207 local_details = conflict->tree_conflict_local_details; 11208 local_moved_to_abspath = 11209 APR_ARRAY_IDX(local_details->wc_move_targets, 11210 local_details->preferred_move_target_idx, const char *); 11211 11212 /* ### The following WC modifications should be atomic. */ 11213 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 11214 &lock_abspath, ctx->wc_ctx, 11215 svn_dirent_get_longest_ancestor(victim_abspath, 11216 local_moved_to_abspath, 11217 scratch_pool), 11218 scratch_pool, scratch_pool)); 11219 11220 /* Get a copy of the incoming moved item's properties. */ 11221 err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, 11222 incoming_moved_to_abspath, 11223 scratch_pool, scratch_pool); 11224 if (err) 11225 goto unlock_wc; 11226 11227 /* Get a copy of the local move target's properties. */ 11228 err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, 11229 local_moved_to_abspath, 11230 scratch_pool, scratch_pool); 11231 if (err) 11232 goto unlock_wc; 11233 11234 /* Create a property diff for the files. */ 11235 err = svn_prop_diffs(&propdiffs, incoming_props, local_props, 11236 scratch_pool); 11237 if (err) 11238 goto unlock_wc; 11239 11240 /* Perform the file merge. */ 11241 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, 11242 ctx->wc_ctx, ancestor_abspath, 11243 incoming_moved_to_abspath, local_moved_to_abspath, 11244 NULL, NULL, NULL, /* labels */ 11245 NULL, NULL, /* conflict versions */ 11246 FALSE, /* dry run */ 11247 NULL, NULL, /* diff3_cmd, merge_options */ 11248 apr_hash_count(ancestor_props) ? ancestor_props : NULL, 11249 propdiffs, 11250 NULL, NULL, /* conflict func/baton */ 11251 NULL, NULL, /* don't allow user to cancel here */ 11252 scratch_pool); 11253 if (err) 11254 goto unlock_wc; 11255 11256 if (ctx->notify_func2) 11257 { 11258 svn_wc_notify_t *notify; 11259 11260 /* Tell the world about the file merge that just happened. */ 11261 notify = svn_wc_create_notify(local_moved_to_abspath, 11262 svn_wc_notify_update_update, 11263 scratch_pool); 11264 if (merge_content_outcome == svn_wc_merge_conflict) 11265 notify->content_state = svn_wc_notify_state_conflicted; 11266 else 11267 notify->content_state = svn_wc_notify_state_merged; 11268 notify->prop_state = merge_props_outcome; 11269 notify->kind = svn_node_file; 11270 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 11271 } 11272 11273 /* Record a new move which overrides the incoming move. */ 11274 err = svn_wc__move2(ctx->wc_ctx, incoming_moved_to_abspath, 11275 local_moved_to_abspath, 11276 TRUE, /* meta-data only move */ 11277 FALSE, /* mixed-revisions don't apply to files */ 11278 NULL, NULL, /* don't allow user to cancel here */ 11279 NULL, NULL, /* no extra notification */ 11280 scratch_pool); 11281 if (err) 11282 goto unlock_wc; 11283 11284 /* Remove moved-away file from disk. */ 11285 err = svn_io_remove_file2(incoming_moved_to_abspath, TRUE, scratch_pool); 11286 if (err) 11287 goto unlock_wc; 11288 11289 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); 11290 if (err) 11291 goto unlock_wc; 11292 11293 if (ctx->notify_func2) 11294 { 11295 svn_wc_notify_t *notify; 11296 11297 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, 11298 scratch_pool); 11299 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 11300 } 11301 11302 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); 11303 11304 conflict->resolution_tree = option_id; 11305 11306unlock_wc: 11307 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 11308 lock_abspath, 11309 scratch_pool)); 11310 SVN_ERR(err); 11311 11312 return SVN_NO_ERROR; 11313} 11314 11315/* Implements conflict_option_resolve_func_t. 11316 * Resolve an incoming move vs local move conflict by merging from the 11317 * local move's target location to the incoming move's target location, 11318 * and reverting the local move. */ 11319static svn_error_t * 11320resolve_both_moved_file_update_keep_incoming_move( 11321 svn_client_conflict_option_t *option, 11322 svn_client_conflict_t *conflict, 11323 svn_client_ctx_t *ctx, 11324 apr_pool_t *scratch_pool) 11325{ 11326 svn_client_conflict_option_id_t option_id; 11327 const char *victim_abspath; 11328 const char *local_moved_to_abspath; 11329 svn_wc_operation_t operation; 11330 const char *lock_abspath; 11331 svn_error_t *err; 11332 const char *repos_root_url; 11333 const char *incoming_old_repos_relpath; 11334 svn_revnum_t incoming_old_pegrev; 11335 const char *incoming_new_repos_relpath; 11336 svn_revnum_t incoming_new_pegrev; 11337 const char *wc_tmpdir; 11338 const char *ancestor_abspath; 11339 svn_stream_t *ancestor_stream; 11340 apr_hash_t *ancestor_props; 11341 apr_hash_t *incoming_props; 11342 apr_hash_t *local_props; 11343 const char *ancestor_url; 11344 const char *corrected_url; 11345 svn_ra_session_t *ra_session; 11346 svn_wc_merge_outcome_t merge_content_outcome; 11347 svn_wc_notify_state_t merge_props_outcome; 11348 apr_array_header_t *propdiffs; 11349 struct conflict_tree_incoming_delete_details *incoming_details; 11350 apr_array_header_t *possible_moved_to_abspaths; 11351 const char *incoming_moved_to_abspath; 11352 struct conflict_tree_update_local_moved_away_details *local_details; 11353 11354 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 11355 operation = svn_client_conflict_get_operation(conflict); 11356 incoming_details = conflict->tree_conflict_incoming_details; 11357 if (incoming_details == NULL || incoming_details->moves == NULL) 11358 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 11359 _("The specified conflict resolution option " 11360 "requires details for tree conflict at '%s' " 11361 "to be fetched from the repository first."), 11362 svn_dirent_local_style(victim_abspath, 11363 scratch_pool)); 11364 if (operation == svn_wc_operation_none) 11365 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 11366 _("Invalid operation code '%d' recorded for " 11367 "conflict at '%s'"), operation, 11368 svn_dirent_local_style(victim_abspath, 11369 scratch_pool)); 11370 11371 option_id = svn_client_conflict_option_get_id(option); 11372 SVN_ERR_ASSERT(option_id == 11373 svn_client_conflict_option_both_moved_file_move_merge); 11374 11375 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, 11376 conflict, scratch_pool, 11377 scratch_pool)); 11378 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 11379 &incoming_old_repos_relpath, &incoming_old_pegrev, 11380 NULL, conflict, scratch_pool, 11381 scratch_pool)); 11382 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 11383 &incoming_new_repos_relpath, &incoming_new_pegrev, 11384 NULL, conflict, scratch_pool, 11385 scratch_pool)); 11386 11387 /* Set up temporary storage for the common ancestor version of the file. */ 11388 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, 11389 scratch_pool, scratch_pool)); 11390 SVN_ERR(svn_stream_open_unique(&ancestor_stream, 11391 &ancestor_abspath, wc_tmpdir, 11392 svn_io_file_del_on_pool_cleanup, 11393 scratch_pool, scratch_pool)); 11394 11395 /* Fetch the ancestor file's content. */ 11396 ancestor_url = svn_path_url_add_component2(repos_root_url, 11397 incoming_old_repos_relpath, 11398 scratch_pool); 11399 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 11400 ancestor_url, NULL, NULL, 11401 FALSE, FALSE, ctx, 11402 scratch_pool, scratch_pool)); 11403 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, 11404 ancestor_stream, NULL, /* fetched_rev */ 11405 &ancestor_props, scratch_pool)); 11406 filter_props(ancestor_props, scratch_pool); 11407 11408 /* Close stream to flush ancestor file to disk. */ 11409 SVN_ERR(svn_stream_close(ancestor_stream)); 11410 11411 possible_moved_to_abspaths = 11412 svn_hash_gets(incoming_details->wc_move_targets, 11413 get_moved_to_repos_relpath(incoming_details, scratch_pool)); 11414 incoming_moved_to_abspath = 11415 APR_ARRAY_IDX(possible_moved_to_abspaths, 11416 incoming_details->wc_move_target_idx, const char *); 11417 11418 local_details = conflict->tree_conflict_local_details; 11419 local_moved_to_abspath = 11420 APR_ARRAY_IDX(local_details->wc_move_targets, 11421 local_details->preferred_move_target_idx, const char *); 11422 11423 /* ### The following WC modifications should be atomic. */ 11424 SVN_ERR(svn_wc__acquire_write_lock_for_resolve( 11425 &lock_abspath, ctx->wc_ctx, 11426 svn_dirent_get_longest_ancestor(victim_abspath, 11427 local_moved_to_abspath, 11428 scratch_pool), 11429 scratch_pool, scratch_pool)); 11430 11431 /* Get a copy of the incoming moved item's properties. */ 11432 err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, 11433 incoming_moved_to_abspath, 11434 scratch_pool, scratch_pool); 11435 if (err) 11436 goto unlock_wc; 11437 11438 /* Get a copy of the local move target's properties. */ 11439 err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, 11440 local_moved_to_abspath, 11441 scratch_pool, scratch_pool); 11442 if (err) 11443 goto unlock_wc; 11444 11445 /* Create a property diff for the files. */ 11446 err = svn_prop_diffs(&propdiffs, incoming_props, local_props, 11447 scratch_pool); 11448 if (err) 11449 goto unlock_wc; 11450 11451 /* Perform the file merge. */ 11452 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, 11453 ctx->wc_ctx, ancestor_abspath, 11454 local_moved_to_abspath, incoming_moved_to_abspath, 11455 NULL, NULL, NULL, /* labels */ 11456 NULL, NULL, /* conflict versions */ 11457 FALSE, /* dry run */ 11458 NULL, NULL, /* diff3_cmd, merge_options */ 11459 apr_hash_count(ancestor_props) ? ancestor_props : NULL, 11460 propdiffs, 11461 NULL, NULL, /* conflict func/baton */ 11462 NULL, NULL, /* don't allow user to cancel here */ 11463 scratch_pool); 11464 if (err) 11465 goto unlock_wc; 11466 11467 if (ctx->notify_func2) 11468 { 11469 svn_wc_notify_t *notify; 11470 11471 /* Tell the world about the file merge that just happened. */ 11472 notify = svn_wc_create_notify(local_moved_to_abspath, 11473 svn_wc_notify_update_update, 11474 scratch_pool); 11475 if (merge_content_outcome == svn_wc_merge_conflict) 11476 notify->content_state = svn_wc_notify_state_conflicted; 11477 else 11478 notify->content_state = svn_wc_notify_state_merged; 11479 notify->prop_state = merge_props_outcome; 11480 notify->kind = svn_node_file; 11481 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 11482 } 11483 11484 /* Revert the copy-half of the local move. The delete-half of this move 11485 * has already been deleted during the update/switch operation. */ 11486 err = svn_wc_revert6(ctx->wc_ctx, local_moved_to_abspath, svn_depth_empty, 11487 FALSE, NULL, TRUE, FALSE, 11488 TRUE /*added_keep_local*/, 11489 NULL, NULL, /* no cancellation */ 11490 ctx->notify_func2, ctx->notify_baton2, 11491 scratch_pool); 11492 if (err) 11493 goto unlock_wc; 11494 11495 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); 11496 if (err) 11497 goto unlock_wc; 11498 11499 if (ctx->notify_func2) 11500 { 11501 svn_wc_notify_t *notify; 11502 11503 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, 11504 scratch_pool); 11505 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 11506 } 11507 11508 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); 11509 11510 conflict->resolution_tree = option_id; 11511 11512unlock_wc: 11513 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, 11514 lock_abspath, 11515 scratch_pool)); 11516 SVN_ERR(err); 11517 11518 return SVN_NO_ERROR; 11519} 11520 11521/* Implements tree_conflict_get_details_func_t. */ 11522static svn_error_t * 11523conflict_tree_get_details_update_local_moved_away( 11524 svn_client_conflict_t *conflict, 11525 svn_client_ctx_t *ctx, 11526 apr_pool_t *scratch_pool) 11527{ 11528 struct conflict_tree_update_local_moved_away_details *details; 11529 const char *incoming_old_repos_relpath; 11530 svn_node_kind_t incoming_old_kind; 11531 11532 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 11533 &incoming_old_repos_relpath, NULL, &incoming_old_kind, 11534 conflict, scratch_pool, scratch_pool)); 11535 11536 details = apr_pcalloc(conflict->pool, sizeof(*details)); 11537 11538 details->wc_move_targets = apr_array_make(conflict->pool, 1, 11539 sizeof(const char *)); 11540 11541 /* Search the WC for copies of the conflict victim. */ 11542 SVN_ERR(svn_wc__find_copies_of_repos_path(&details->wc_move_targets, 11543 conflict->local_abspath, 11544 incoming_old_repos_relpath, 11545 incoming_old_kind, 11546 ctx->wc_ctx, 11547 conflict->pool, 11548 scratch_pool)); 11549 11550 conflict->tree_conflict_local_details = details; 11551 11552 return SVN_NO_ERROR; 11553} 11554 11555static svn_error_t * 11556get_both_moved_file_paths(const char **incoming_moved_to_abspath, 11557 const char **local_moved_to_abspath, 11558 svn_client_conflict_t *conflict, 11559 apr_pool_t *scratch_pool) 11560{ 11561 struct conflict_tree_incoming_delete_details *incoming_details; 11562 apr_array_header_t *incoming_move_target_wc_abspaths; 11563 svn_wc_operation_t operation; 11564 11565 operation = svn_client_conflict_get_operation(conflict); 11566 11567 *incoming_moved_to_abspath = NULL; 11568 *local_moved_to_abspath = NULL; 11569 11570 incoming_details = conflict->tree_conflict_incoming_details; 11571 if (incoming_details == NULL || incoming_details->moves == NULL || 11572 apr_hash_count(incoming_details->wc_move_targets) == 0) 11573 return SVN_NO_ERROR; 11574 11575 incoming_move_target_wc_abspaths = 11576 svn_hash_gets(incoming_details->wc_move_targets, 11577 get_moved_to_repos_relpath(incoming_details, 11578 scratch_pool)); 11579 *incoming_moved_to_abspath = 11580 APR_ARRAY_IDX(incoming_move_target_wc_abspaths, 11581 incoming_details->wc_move_target_idx, const char *); 11582 11583 if (operation == svn_wc_operation_merge) 11584 { 11585 struct conflict_tree_local_missing_details *local_details; 11586 apr_array_header_t *local_moves; 11587 11588 local_details = conflict->tree_conflict_local_details; 11589 if (local_details == NULL || 11590 apr_hash_count(local_details->wc_move_targets) == 0) 11591 return SVN_NO_ERROR; 11592 11593 local_moves = svn_hash_gets(local_details->wc_move_targets, 11594 local_details->move_target_repos_relpath); 11595 *local_moved_to_abspath = 11596 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, 11597 const char *); 11598 } 11599 else 11600 { 11601 struct conflict_tree_update_local_moved_away_details *local_details; 11602 11603 local_details = conflict->tree_conflict_local_details; 11604 if (local_details == NULL || 11605 local_details->wc_move_targets->nelts == 0) 11606 return SVN_NO_ERROR; 11607 11608 *local_moved_to_abspath = 11609 APR_ARRAY_IDX(local_details->wc_move_targets, 11610 local_details->preferred_move_target_idx, 11611 const char *); 11612 } 11613 11614 return SVN_NO_ERROR; 11615} 11616 11617static svn_error_t * 11618conflict_tree_get_description_update_both_moved_file_merge( 11619 const char **description, 11620 svn_client_conflict_t *conflict, 11621 svn_client_ctx_t *ctx, 11622 apr_pool_t *result_pool, 11623 apr_pool_t *scratch_pool) 11624{ 11625 const char *incoming_moved_to_abspath; 11626 const char *local_moved_to_abspath; 11627 svn_wc_operation_t operation; 11628 const char *wcroot_abspath; 11629 11630 *description = NULL; 11631 11632 SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath, 11633 &local_moved_to_abspath, 11634 conflict, scratch_pool)); 11635 if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL) 11636 return SVN_NO_ERROR; 11637 11638 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 11639 conflict->local_abspath, scratch_pool, 11640 scratch_pool)); 11641 11642 operation = svn_client_conflict_get_operation(conflict); 11643 11644 if (operation == svn_wc_operation_merge) 11645 { 11646 /* In case of a merge, the incoming move has A+ (copied) status... */ 11647 *description = 11648 apr_psprintf( 11649 scratch_pool, 11650 _("apply changes to '%s' and revert addition of '%s'"), 11651 svn_dirent_local_style( 11652 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), 11653 scratch_pool), 11654 svn_dirent_local_style( 11655 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), 11656 scratch_pool)); 11657 } 11658 else 11659 { 11660 /* ...but in case of update/switch the local move has "A+" status. */ 11661 *description = 11662 apr_psprintf( 11663 scratch_pool, 11664 _("override incoming move and merge incoming changes from '%s' " 11665 "to '%s'"), 11666 svn_dirent_local_style( 11667 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), 11668 scratch_pool), 11669 svn_dirent_local_style( 11670 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), 11671 scratch_pool)); 11672 } 11673 11674 return SVN_NO_ERROR; 11675} 11676 11677static svn_error_t * 11678conflict_tree_get_description_update_both_moved_file_move_merge( 11679 const char **description, 11680 svn_client_conflict_t *conflict, 11681 svn_client_ctx_t *ctx, 11682 apr_pool_t *result_pool, 11683 apr_pool_t *scratch_pool) 11684{ 11685 const char *incoming_moved_to_abspath; 11686 const char *local_moved_to_abspath; 11687 svn_wc_operation_t operation; 11688 const char *wcroot_abspath; 11689 11690 *description = NULL; 11691 11692 SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath, 11693 &local_moved_to_abspath, 11694 conflict, scratch_pool)); 11695 if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL) 11696 return SVN_NO_ERROR; 11697 11698 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 11699 conflict->local_abspath, scratch_pool, 11700 scratch_pool)); 11701 11702 operation = svn_client_conflict_get_operation(conflict); 11703 11704 if (operation == svn_wc_operation_merge) 11705 { 11706 SVN_ERR(describe_incoming_move_merge_conflict_option( 11707 description, conflict, ctx, local_moved_to_abspath, 11708 scratch_pool, scratch_pool)); 11709 } 11710 else 11711 { 11712 *description = 11713 apr_psprintf( 11714 scratch_pool, 11715 _("accept incoming move and merge local changes from " 11716 "'%s' to '%s'"), 11717 svn_dirent_local_style( 11718 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), 11719 scratch_pool), 11720 svn_dirent_local_style( 11721 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), 11722 scratch_pool)); 11723 } 11724 11725 return SVN_NO_ERROR; 11726} 11727 11728/* Configure 'both moved file merge' resolution options for a tree conflict. */ 11729static svn_error_t * 11730configure_option_both_moved_file_merge(svn_client_conflict_t *conflict, 11731 svn_client_ctx_t *ctx, 11732 apr_array_header_t *options, 11733 apr_pool_t *scratch_pool) 11734{ 11735 svn_wc_operation_t operation; 11736 svn_node_kind_t victim_node_kind; 11737 svn_wc_conflict_action_t incoming_change; 11738 svn_wc_conflict_reason_t local_change; 11739 const char *incoming_old_repos_relpath; 11740 svn_revnum_t incoming_old_pegrev; 11741 svn_node_kind_t incoming_old_kind; 11742 const char *incoming_new_repos_relpath; 11743 svn_revnum_t incoming_new_pegrev; 11744 svn_node_kind_t incoming_new_kind; 11745 const char *wcroot_abspath; 11746 11747 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 11748 conflict->local_abspath, scratch_pool, 11749 scratch_pool)); 11750 11751 operation = svn_client_conflict_get_operation(conflict); 11752 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 11753 incoming_change = svn_client_conflict_get_incoming_change(conflict); 11754 local_change = svn_client_conflict_get_local_change(conflict); 11755 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 11756 &incoming_old_repos_relpath, &incoming_old_pegrev, 11757 &incoming_old_kind, conflict, scratch_pool, 11758 scratch_pool)); 11759 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 11760 &incoming_new_repos_relpath, &incoming_new_pegrev, 11761 &incoming_new_kind, conflict, scratch_pool, 11762 scratch_pool)); 11763 11764 /* ### what about the switch operation? */ 11765 if (((operation == svn_wc_operation_merge && 11766 victim_node_kind == svn_node_none) || 11767 (operation == svn_wc_operation_update && 11768 victim_node_kind == svn_node_file)) && 11769 incoming_old_kind == svn_node_file && 11770 incoming_new_kind == svn_node_none && 11771 ((operation == svn_wc_operation_merge && 11772 local_change == svn_wc_conflict_reason_missing) || 11773 (operation == svn_wc_operation_update && 11774 local_change == svn_wc_conflict_reason_moved_away)) && 11775 incoming_change == svn_wc_conflict_action_delete) 11776 { 11777 const char *description; 11778 11779 SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge( 11780 &description, conflict, ctx, conflict->pool, scratch_pool)); 11781 11782 if (description == NULL) /* details not fetched yet */ 11783 return SVN_NO_ERROR; 11784 11785 add_resolution_option( 11786 options, conflict, svn_client_conflict_option_both_moved_file_merge, 11787 _("Merge to corresponding local location"), 11788 description, 11789 operation == svn_wc_operation_merge ? 11790 resolve_both_moved_file_text_merge : 11791 resolve_both_moved_file_update_keep_local_move); 11792 11793 SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge( 11794 &description, conflict, ctx, conflict->pool, scratch_pool)); 11795 11796 add_resolution_option(options, conflict, 11797 svn_client_conflict_option_both_moved_file_move_merge, 11798 _("Move and merge"), description, 11799 operation == svn_wc_operation_merge ? 11800 resolve_incoming_move_file_text_merge : 11801 resolve_both_moved_file_update_keep_incoming_move); 11802 } 11803 11804 return SVN_NO_ERROR; 11805} 11806 11807/* Configure 'both moved dir merge' resolution options for a tree conflict. */ 11808static svn_error_t * 11809configure_option_both_moved_dir_merge(svn_client_conflict_t *conflict, 11810 svn_client_ctx_t *ctx, 11811 apr_array_header_t *options, 11812 apr_pool_t *scratch_pool) 11813{ 11814 svn_wc_operation_t operation; 11815 svn_node_kind_t victim_node_kind; 11816 svn_wc_conflict_action_t incoming_change; 11817 svn_wc_conflict_reason_t local_change; 11818 const char *incoming_old_repos_relpath; 11819 svn_revnum_t incoming_old_pegrev; 11820 svn_node_kind_t incoming_old_kind; 11821 const char *incoming_new_repos_relpath; 11822 svn_revnum_t incoming_new_pegrev; 11823 svn_node_kind_t incoming_new_kind; 11824 const char *wcroot_abspath; 11825 11826 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 11827 conflict->local_abspath, scratch_pool, 11828 scratch_pool)); 11829 11830 operation = svn_client_conflict_get_operation(conflict); 11831 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); 11832 incoming_change = svn_client_conflict_get_incoming_change(conflict); 11833 local_change = svn_client_conflict_get_local_change(conflict); 11834 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( 11835 &incoming_old_repos_relpath, &incoming_old_pegrev, 11836 &incoming_old_kind, conflict, scratch_pool, 11837 scratch_pool)); 11838 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( 11839 &incoming_new_repos_relpath, &incoming_new_pegrev, 11840 &incoming_new_kind, conflict, scratch_pool, 11841 scratch_pool)); 11842 11843 if (operation == svn_wc_operation_merge && 11844 victim_node_kind == svn_node_none && 11845 incoming_old_kind == svn_node_dir && 11846 incoming_new_kind == svn_node_none && 11847 local_change == svn_wc_conflict_reason_missing && 11848 incoming_change == svn_wc_conflict_action_delete) 11849 { 11850 struct conflict_tree_incoming_delete_details *incoming_details; 11851 struct conflict_tree_local_missing_details *local_details; 11852 const char *description; 11853 apr_array_header_t *local_moves; 11854 const char *local_moved_to_abspath; 11855 const char *incoming_moved_to_abspath; 11856 apr_array_header_t *incoming_move_target_wc_abspaths; 11857 11858 incoming_details = conflict->tree_conflict_incoming_details; 11859 if (incoming_details == NULL || incoming_details->moves == NULL || 11860 apr_hash_count(incoming_details->wc_move_targets) == 0) 11861 return SVN_NO_ERROR; 11862 11863 local_details = conflict->tree_conflict_local_details; 11864 if (local_details == NULL || 11865 apr_hash_count(local_details->wc_move_targets) == 0) 11866 return SVN_NO_ERROR; 11867 11868 local_moves = svn_hash_gets(local_details->wc_move_targets, 11869 local_details->move_target_repos_relpath); 11870 local_moved_to_abspath = 11871 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, 11872 const char *); 11873 11874 incoming_move_target_wc_abspaths = 11875 svn_hash_gets(incoming_details->wc_move_targets, 11876 get_moved_to_repos_relpath(incoming_details, 11877 scratch_pool)); 11878 incoming_moved_to_abspath = 11879 APR_ARRAY_IDX(incoming_move_target_wc_abspaths, 11880 incoming_details->wc_move_target_idx, const char *); 11881 11882 description = 11883 apr_psprintf( 11884 scratch_pool, _("apply changes to '%s' and revert addition of '%s'"), 11885 svn_dirent_local_style( 11886 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), 11887 scratch_pool), 11888 svn_dirent_local_style( 11889 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), 11890 scratch_pool)); 11891 add_resolution_option( 11892 options, conflict, svn_client_conflict_option_both_moved_dir_merge, 11893 _("Merge to corresponding local location"), 11894 description, resolve_both_moved_dir_merge); 11895 11896 SVN_ERR(describe_incoming_move_merge_conflict_option( 11897 &description, conflict, ctx, local_moved_to_abspath, 11898 scratch_pool, scratch_pool)); 11899 add_resolution_option(options, conflict, 11900 svn_client_conflict_option_both_moved_dir_move_merge, 11901 _("Move and merge"), description, 11902 resolve_both_moved_dir_move_merge); 11903 } 11904 11905 return SVN_NO_ERROR; 11906} 11907 11908/* Return a copy of the repos replath candidate list. */ 11909static svn_error_t * 11910get_repos_relpath_candidates( 11911 apr_array_header_t **possible_moved_to_repos_relpaths, 11912 apr_hash_t *wc_move_targets, 11913 apr_pool_t *result_pool, 11914 apr_pool_t *scratch_pool) 11915{ 11916 apr_array_header_t *sorted_repos_relpaths; 11917 int i; 11918 11919 sorted_repos_relpaths = svn_sort__hash(wc_move_targets, 11920 svn_sort_compare_items_as_paths, 11921 scratch_pool); 11922 11923 *possible_moved_to_repos_relpaths = 11924 apr_array_make(result_pool, sorted_repos_relpaths->nelts, 11925 sizeof (const char *)); 11926 for (i = 0; i < sorted_repos_relpaths->nelts; i++) 11927 { 11928 svn_sort__item_t item; 11929 const char *repos_relpath; 11930 11931 item = APR_ARRAY_IDX(sorted_repos_relpaths, i, svn_sort__item_t); 11932 repos_relpath = item.key; 11933 APR_ARRAY_PUSH(*possible_moved_to_repos_relpaths, const char *) = 11934 apr_pstrdup(result_pool, repos_relpath); 11935 } 11936 11937 return SVN_NO_ERROR; 11938} 11939 11940svn_error_t * 11941svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( 11942 apr_array_header_t **possible_moved_to_repos_relpaths, 11943 svn_client_conflict_option_t *option, 11944 apr_pool_t *result_pool, 11945 apr_pool_t *scratch_pool) 11946{ 11947 svn_client_conflict_t *conflict = option->conflict; 11948 const char *victim_abspath; 11949 svn_wc_operation_t operation; 11950 svn_wc_conflict_action_t incoming_change; 11951 svn_wc_conflict_reason_t local_change; 11952 svn_client_conflict_option_id_t id; 11953 11954 id = svn_client_conflict_option_get_id(option); 11955 if (id != svn_client_conflict_option_incoming_move_file_text_merge && 11956 id != svn_client_conflict_option_incoming_move_dir_merge && 11957 id != svn_client_conflict_option_local_move_file_text_merge && 11958 id != svn_client_conflict_option_local_move_dir_merge && 11959 id != svn_client_conflict_option_sibling_move_file_text_merge && 11960 id != svn_client_conflict_option_sibling_move_dir_merge && 11961 id != svn_client_conflict_option_both_moved_file_merge && 11962 id != svn_client_conflict_option_both_moved_file_move_merge && 11963 id != svn_client_conflict_option_both_moved_dir_merge && 11964 id != svn_client_conflict_option_both_moved_dir_move_merge) 11965 { 11966 /* We cannot operate on this option. */ 11967 *possible_moved_to_repos_relpaths = NULL; 11968 return SVN_NO_ERROR; 11969 } 11970 11971 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 11972 operation = svn_client_conflict_get_operation(conflict); 11973 incoming_change = svn_client_conflict_get_incoming_change(conflict); 11974 local_change = svn_client_conflict_get_local_change(conflict); 11975 11976 if (operation == svn_wc_operation_merge && 11977 incoming_change == svn_wc_conflict_action_edit && 11978 local_change == svn_wc_conflict_reason_missing) 11979 { 11980 struct conflict_tree_local_missing_details *details; 11981 11982 details = conflict->tree_conflict_local_details; 11983 if (details == NULL || 11984 (details->wc_move_targets == NULL && details->wc_siblings == NULL)) 11985 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 11986 _("Getting a list of possible move targets " 11987 "requires details for tree conflict at '%s' " 11988 "to be fetched from the repository first"), 11989 svn_dirent_local_style(victim_abspath, 11990 scratch_pool)); 11991 11992 if (details->wc_move_targets) 11993 SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths, 11994 details->wc_move_targets, 11995 result_pool, scratch_pool)); 11996 else 11997 *possible_moved_to_repos_relpaths = NULL; 11998 } 11999 else 12000 { 12001 struct conflict_tree_incoming_delete_details *details; 12002 12003 details = conflict->tree_conflict_incoming_details; 12004 if (details == NULL || details->wc_move_targets == NULL) 12005 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12006 _("Getting a list of possible move targets " 12007 "requires details for tree conflict at '%s' " 12008 "to be fetched from the repository first"), 12009 svn_dirent_local_style(victim_abspath, 12010 scratch_pool)); 12011 12012 SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths, 12013 details->wc_move_targets, 12014 result_pool, scratch_pool)); 12015 } 12016 12017 return SVN_NO_ERROR; 12018} 12019 12020svn_error_t * 12021svn_client_conflict_option_get_moved_to_repos_relpath_candidates( 12022 apr_array_header_t **possible_moved_to_repos_relpaths, 12023 svn_client_conflict_option_t *option, 12024 apr_pool_t *result_pool, 12025 apr_pool_t *scratch_pool) 12026{ 12027 /* The only difference to API version 2 is an assertion failure if 12028 * an unexpected option is passed. 12029 * We do not emulate this old behaviour since clients written against 12030 * the previous API will just keep working. */ 12031 return svn_error_trace( 12032 svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( 12033 possible_moved_to_repos_relpaths, option, result_pool, scratch_pool)); 12034} 12035 12036static svn_error_t * 12037set_wc_move_target(const char **new_hash_key, 12038 apr_hash_t *wc_move_targets, 12039 int preferred_move_target_idx, 12040 const char *victim_abspath, 12041 apr_pool_t *scratch_pool) 12042{ 12043 apr_array_header_t *move_target_repos_relpaths; 12044 svn_sort__item_t item; 12045 const char *move_target_repos_relpath; 12046 apr_hash_index_t *hi; 12047 12048 if (preferred_move_target_idx < 0 || 12049 preferred_move_target_idx >= apr_hash_count(wc_move_targets)) 12050 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 12051 _("Index '%d' is out of bounds of the possible " 12052 "move target list for '%s'"), 12053 preferred_move_target_idx, 12054 svn_dirent_local_style(victim_abspath, 12055 scratch_pool)); 12056 12057 /* Translate the index back into a hash table key. */ 12058 move_target_repos_relpaths = svn_sort__hash(wc_move_targets, 12059 svn_sort_compare_items_as_paths, 12060 scratch_pool); 12061 item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx, 12062 svn_sort__item_t); 12063 move_target_repos_relpath = item.key; 12064 /* Find our copy of the hash key and remember the user's preference. */ 12065 for (hi = apr_hash_first(scratch_pool, wc_move_targets); 12066 hi != NULL; 12067 hi = apr_hash_next(hi)) 12068 { 12069 const char *repos_relpath = apr_hash_this_key(hi); 12070 12071 if (strcmp(move_target_repos_relpath, repos_relpath) == 0) 12072 { 12073 *new_hash_key = repos_relpath; 12074 return SVN_NO_ERROR; 12075 } 12076 } 12077 12078 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 12079 _("Repository path '%s' not found in list of " 12080 "possible move targets for '%s'"), 12081 move_target_repos_relpath, 12082 svn_dirent_local_style(victim_abspath, 12083 scratch_pool)); 12084} 12085 12086svn_error_t * 12087svn_client_conflict_option_set_moved_to_repos_relpath2( 12088 svn_client_conflict_option_t *option, 12089 int preferred_move_target_idx, 12090 svn_client_ctx_t *ctx, 12091 apr_pool_t *scratch_pool) 12092{ 12093 svn_client_conflict_t *conflict = option->conflict; 12094 const char *victim_abspath; 12095 svn_wc_operation_t operation; 12096 svn_wc_conflict_action_t incoming_change; 12097 svn_wc_conflict_reason_t local_change; 12098 svn_client_conflict_option_id_t id; 12099 12100 id = svn_client_conflict_option_get_id(option); 12101 if (id != svn_client_conflict_option_incoming_move_file_text_merge && 12102 id != svn_client_conflict_option_incoming_move_dir_merge && 12103 id != svn_client_conflict_option_local_move_file_text_merge && 12104 id != svn_client_conflict_option_local_move_dir_merge && 12105 id != svn_client_conflict_option_sibling_move_file_text_merge && 12106 id != svn_client_conflict_option_sibling_move_dir_merge && 12107 id != svn_client_conflict_option_both_moved_file_merge && 12108 id != svn_client_conflict_option_both_moved_file_move_merge && 12109 id != svn_client_conflict_option_both_moved_dir_merge && 12110 id != svn_client_conflict_option_both_moved_dir_move_merge) 12111 return SVN_NO_ERROR; /* We cannot operate on this option. Nothing to do. */ 12112 12113 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 12114 operation = svn_client_conflict_get_operation(conflict); 12115 incoming_change = svn_client_conflict_get_incoming_change(conflict); 12116 local_change = svn_client_conflict_get_local_change(conflict); 12117 12118 if (operation == svn_wc_operation_merge && 12119 incoming_change == svn_wc_conflict_action_edit && 12120 local_change == svn_wc_conflict_reason_missing) 12121 { 12122 struct conflict_tree_local_missing_details *details; 12123 12124 details = conflict->tree_conflict_local_details; 12125 if (details == NULL || details->wc_move_targets == NULL) 12126 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12127 _("Setting a move target requires details " 12128 "for tree conflict at '%s' to be fetched " 12129 "from the repository first"), 12130 svn_dirent_local_style(victim_abspath, 12131 scratch_pool)); 12132 12133 SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath, 12134 details->wc_move_targets, 12135 preferred_move_target_idx, 12136 victim_abspath, scratch_pool)); 12137 details->wc_move_target_idx = 0; 12138 12139 /* Update option description. */ 12140 SVN_ERR(conflict_tree_get_description_local_missing( 12141 &option->description, conflict, ctx, 12142 conflict->pool, scratch_pool)); 12143 } 12144 else 12145 { 12146 struct conflict_tree_incoming_delete_details *details; 12147 apr_array_header_t *move_target_wc_abspaths; 12148 const char *moved_to_abspath; 12149 12150 details = conflict->tree_conflict_incoming_details; 12151 if (details == NULL || details->wc_move_targets == NULL) 12152 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12153 _("Setting a move target requires details " 12154 "for tree conflict at '%s' to be fetched " 12155 "from the repository first"), 12156 svn_dirent_local_style(victim_abspath, 12157 scratch_pool)); 12158 12159 SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath, 12160 details->wc_move_targets, 12161 preferred_move_target_idx, 12162 victim_abspath, scratch_pool)); 12163 details->wc_move_target_idx = 0; 12164 12165 /* Update option description. */ 12166 move_target_wc_abspaths = 12167 svn_hash_gets(details->wc_move_targets, 12168 get_moved_to_repos_relpath(details, scratch_pool)); 12169 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, 12170 details->wc_move_target_idx, 12171 const char *); 12172 SVN_ERR(describe_incoming_move_merge_conflict_option( 12173 &option->description, 12174 conflict, ctx, 12175 moved_to_abspath, 12176 conflict->pool, 12177 scratch_pool)); 12178 } 12179 12180 return SVN_NO_ERROR; 12181} 12182 12183svn_error_t * 12184svn_client_conflict_option_set_moved_to_repos_relpath( 12185 svn_client_conflict_option_t *option, 12186 int preferred_move_target_idx, 12187 svn_client_ctx_t *ctx, 12188 apr_pool_t *scratch_pool) 12189{ 12190 /* The only difference to API version 2 is an assertion failure if 12191 * an unexpected option is passed. 12192 * We do not emulate this old behaviour since clients written against 12193 * the previous API will just keep working. */ 12194 return svn_error_trace( 12195 svn_client_conflict_option_set_moved_to_repos_relpath2(option, 12196 preferred_move_target_idx, ctx, scratch_pool)); 12197} 12198 12199svn_error_t * 12200svn_client_conflict_option_get_moved_to_abspath_candidates2( 12201 apr_array_header_t **possible_moved_to_abspaths, 12202 svn_client_conflict_option_t *option, 12203 apr_pool_t *result_pool, 12204 apr_pool_t *scratch_pool) 12205{ 12206 svn_client_conflict_t *conflict = option->conflict; 12207 const char *victim_abspath; 12208 svn_wc_operation_t operation; 12209 svn_wc_conflict_action_t incoming_change; 12210 svn_wc_conflict_reason_t local_change; 12211 int i; 12212 svn_client_conflict_option_id_t id; 12213 12214 id = svn_client_conflict_option_get_id(option); 12215 if (id != svn_client_conflict_option_incoming_move_file_text_merge && 12216 id != svn_client_conflict_option_incoming_move_dir_merge && 12217 id != svn_client_conflict_option_local_move_file_text_merge && 12218 id != svn_client_conflict_option_local_move_dir_merge && 12219 id != svn_client_conflict_option_sibling_move_file_text_merge && 12220 id != svn_client_conflict_option_sibling_move_dir_merge && 12221 id != svn_client_conflict_option_both_moved_file_merge && 12222 id != svn_client_conflict_option_both_moved_file_move_merge && 12223 id != svn_client_conflict_option_both_moved_dir_merge && 12224 id != svn_client_conflict_option_both_moved_dir_move_merge) 12225 { 12226 /* We cannot operate on this option. */ 12227 *possible_moved_to_abspaths = NULL; 12228 return NULL; 12229 } 12230 12231 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 12232 operation = svn_client_conflict_get_operation(conflict); 12233 incoming_change = svn_client_conflict_get_incoming_change(conflict); 12234 local_change = svn_client_conflict_get_local_change(conflict); 12235 12236 if (operation == svn_wc_operation_merge && 12237 incoming_change == svn_wc_conflict_action_edit && 12238 local_change == svn_wc_conflict_reason_missing) 12239 { 12240 struct conflict_tree_local_missing_details *details; 12241 12242 details = conflict->tree_conflict_local_details; 12243 if (details == NULL || 12244 (details->wc_move_targets == NULL && details->wc_siblings == NULL)) 12245 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12246 _("Getting a list of possible move siblings " 12247 "requires details for tree conflict at '%s' " 12248 "to be fetched from the repository first"), 12249 svn_dirent_local_style(victim_abspath, 12250 scratch_pool)); 12251 12252 *possible_moved_to_abspaths = apr_array_make(result_pool, 1, 12253 sizeof (const char *)); 12254 if (details->wc_move_targets) 12255 { 12256 apr_array_header_t *move_target_wc_abspaths; 12257 move_target_wc_abspaths = 12258 svn_hash_gets(details->wc_move_targets, 12259 details->move_target_repos_relpath); 12260 for (i = 0; i < move_target_wc_abspaths->nelts; i++) 12261 { 12262 const char *moved_to_abspath; 12263 12264 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i, 12265 const char *); 12266 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = 12267 apr_pstrdup(result_pool, moved_to_abspath); 12268 } 12269 } 12270 12271 /* ### Siblings are actually 'corresponding nodes', not 'move targets'. 12272 ### But we provide them here to avoid another API function. */ 12273 if (details->wc_siblings) 12274 { 12275 for (i = 0; i < details->wc_siblings->nelts; i++) 12276 { 12277 const char *sibling_abspath; 12278 12279 sibling_abspath = APR_ARRAY_IDX(details->wc_siblings, i, 12280 const char *); 12281 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = 12282 apr_pstrdup(result_pool, sibling_abspath); 12283 } 12284 } 12285 } 12286 else if ((operation == svn_wc_operation_update || 12287 operation == svn_wc_operation_switch) && 12288 incoming_change == svn_wc_conflict_action_delete && 12289 local_change == svn_wc_conflict_reason_moved_away) 12290 { 12291 struct conflict_tree_update_local_moved_away_details *details; 12292 12293 details = conflict->tree_conflict_local_details; 12294 if (details == NULL || details->wc_move_targets == NULL) 12295 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12296 _("Getting a list of possible move targets " 12297 "requires details for tree conflict at '%s' " 12298 "to be fetched from the repository first"), 12299 svn_dirent_local_style(victim_abspath, 12300 scratch_pool)); 12301 12302 /* Return a copy of the option's move target candidate list. */ 12303 *possible_moved_to_abspaths = 12304 apr_array_make(result_pool, details->wc_move_targets->nelts, 12305 sizeof (const char *)); 12306 for (i = 0; i < details->wc_move_targets->nelts; i++) 12307 { 12308 const char *moved_to_abspath; 12309 12310 moved_to_abspath = APR_ARRAY_IDX(details->wc_move_targets, i, 12311 const char *); 12312 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = 12313 apr_pstrdup(result_pool, moved_to_abspath); 12314 } 12315 } 12316 else 12317 { 12318 struct conflict_tree_incoming_delete_details *details; 12319 apr_array_header_t *move_target_wc_abspaths; 12320 12321 details = conflict->tree_conflict_incoming_details; 12322 if (details == NULL || details->wc_move_targets == NULL) 12323 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12324 _("Getting a list of possible move targets " 12325 "requires details for tree conflict at '%s' " 12326 "to be fetched from the repository first"), 12327 svn_dirent_local_style(victim_abspath, 12328 scratch_pool)); 12329 12330 move_target_wc_abspaths = 12331 svn_hash_gets(details->wc_move_targets, 12332 get_moved_to_repos_relpath(details, scratch_pool)); 12333 12334 /* Return a copy of the option's move target candidate list. */ 12335 *possible_moved_to_abspaths = 12336 apr_array_make(result_pool, move_target_wc_abspaths->nelts, 12337 sizeof (const char *)); 12338 for (i = 0; i < move_target_wc_abspaths->nelts; i++) 12339 { 12340 const char *moved_to_abspath; 12341 12342 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i, 12343 const char *); 12344 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = 12345 apr_pstrdup(result_pool, moved_to_abspath); 12346 } 12347 } 12348 12349 return SVN_NO_ERROR; 12350} 12351 12352svn_error_t * 12353svn_client_conflict_option_get_moved_to_abspath_candidates( 12354 apr_array_header_t **possible_moved_to_abspaths, 12355 svn_client_conflict_option_t *option, 12356 apr_pool_t *result_pool, 12357 apr_pool_t *scratch_pool) 12358{ 12359 /* The only difference to API version 2 is an assertion failure if 12360 * an unexpected option is passed. 12361 * We do not emulate this old behaviour since clients written against 12362 * the previous API will just keep working. */ 12363 return svn_error_trace( 12364 svn_client_conflict_option_get_moved_to_abspath_candidates2( 12365 possible_moved_to_abspaths, option, result_pool, scratch_pool)); 12366} 12367 12368svn_error_t * 12369svn_client_conflict_option_set_moved_to_abspath2( 12370 svn_client_conflict_option_t *option, 12371 int preferred_move_target_idx, 12372 svn_client_ctx_t *ctx, 12373 apr_pool_t *scratch_pool) 12374{ 12375 svn_client_conflict_t *conflict = option->conflict; 12376 const char *victim_abspath; 12377 svn_wc_operation_t operation; 12378 svn_wc_conflict_action_t incoming_change; 12379 svn_wc_conflict_reason_t local_change; 12380 svn_client_conflict_option_id_t id; 12381 12382 id = svn_client_conflict_option_get_id(option); 12383 if (id != svn_client_conflict_option_incoming_move_file_text_merge && 12384 id != svn_client_conflict_option_incoming_move_dir_merge && 12385 id != svn_client_conflict_option_local_move_file_text_merge && 12386 id != svn_client_conflict_option_local_move_dir_merge && 12387 id != svn_client_conflict_option_sibling_move_file_text_merge && 12388 id != svn_client_conflict_option_sibling_move_dir_merge && 12389 id != svn_client_conflict_option_both_moved_file_merge && 12390 id != svn_client_conflict_option_both_moved_file_move_merge && 12391 id != svn_client_conflict_option_both_moved_dir_merge && 12392 id != svn_client_conflict_option_both_moved_dir_move_merge) 12393 return NULL; /* We cannot operate on this option. Nothing to do. */ 12394 12395 victim_abspath = svn_client_conflict_get_local_abspath(conflict); 12396 operation = svn_client_conflict_get_operation(conflict); 12397 incoming_change = svn_client_conflict_get_incoming_change(conflict); 12398 local_change = svn_client_conflict_get_local_change(conflict); 12399 12400 if (operation == svn_wc_operation_merge && 12401 incoming_change == svn_wc_conflict_action_edit && 12402 local_change == svn_wc_conflict_reason_missing) 12403 { 12404 struct conflict_tree_local_missing_details *details; 12405 const char *wcroot_abspath; 12406 const char *preferred_sibling; 12407 12408 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, 12409 ctx->wc_ctx, 12410 conflict->local_abspath, 12411 scratch_pool, 12412 scratch_pool)); 12413 12414 details = conflict->tree_conflict_local_details; 12415 if (details == NULL || (details->wc_siblings == NULL && 12416 details->wc_move_targets == NULL)) 12417 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12418 _("Setting a move target requires details " 12419 "for tree conflict at '%s' to be fetched " 12420 "from the repository first"), 12421 svn_dirent_local_style(victim_abspath, 12422 scratch_pool)); 12423 12424 if (details->wc_siblings) 12425 { 12426 if (preferred_move_target_idx < 0 || 12427 preferred_move_target_idx > details->wc_siblings->nelts) 12428 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 12429 _("Index '%d' is out of bounds of the " 12430 "possible move sibling list for '%s'"), 12431 preferred_move_target_idx, 12432 svn_dirent_local_style(victim_abspath, 12433 scratch_pool)); 12434 /* Record the user's preference. */ 12435 details->preferred_sibling_idx = preferred_move_target_idx; 12436 12437 /* Update option description. */ 12438 preferred_sibling = APR_ARRAY_IDX(details->wc_siblings, 12439 details->preferred_sibling_idx, 12440 const char *); 12441 option->description = 12442 apr_psprintf( 12443 conflict->pool, _("apply changes to '%s'"), 12444 svn_dirent_local_style( 12445 svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling), 12446 scratch_pool)); 12447 } 12448 else if (details->wc_move_targets) 12449 { 12450 apr_array_header_t *move_target_wc_abspaths; 12451 move_target_wc_abspaths = 12452 svn_hash_gets(details->wc_move_targets, 12453 details->move_target_repos_relpath); 12454 12455 if (preferred_move_target_idx < 0 || 12456 preferred_move_target_idx > move_target_wc_abspaths->nelts) 12457 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 12458 _("Index '%d' is out of bounds of the possible " 12459 "move target list for '%s'"), 12460 preferred_move_target_idx, 12461 svn_dirent_local_style(victim_abspath, 12462 scratch_pool)); 12463 12464 /* Record the user's preference. */ 12465 details->wc_move_target_idx = preferred_move_target_idx; 12466 12467 /* Update option description. */ 12468 SVN_ERR(conflict_tree_get_description_local_missing( 12469 &option->description, conflict, ctx, 12470 conflict->pool, scratch_pool)); 12471 } 12472 } 12473 else if ((operation == svn_wc_operation_update || 12474 operation == svn_wc_operation_switch) && 12475 incoming_change == svn_wc_conflict_action_delete && 12476 local_change == svn_wc_conflict_reason_moved_away) 12477 { 12478 struct conflict_tree_update_local_moved_away_details *details; 12479 12480 details = conflict->tree_conflict_local_details; 12481 if (details == NULL || details->wc_move_targets == NULL) 12482 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12483 _("Setting a move target requires details " 12484 "for tree conflict at '%s' to be fetched " 12485 "from the repository first"), 12486 svn_dirent_local_style(victim_abspath, 12487 scratch_pool)); 12488 12489 if (preferred_move_target_idx < 0 || 12490 preferred_move_target_idx > details->wc_move_targets->nelts) 12491 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 12492 _("Index '%d' is out of bounds of the " 12493 "possible move target list for '%s'"), 12494 preferred_move_target_idx, 12495 svn_dirent_local_style(victim_abspath, 12496 scratch_pool)); 12497 12498 /* Record the user's preference. */ 12499 details->preferred_move_target_idx = preferred_move_target_idx; 12500 12501 /* Update option description. */ 12502 if (id == svn_client_conflict_option_both_moved_file_merge) 12503 SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge( 12504 &option->description, conflict, ctx, conflict->pool, 12505 scratch_pool)); 12506 else if (id == svn_client_conflict_option_both_moved_file_move_merge) 12507 SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge( 12508 &option->description, conflict, ctx, conflict->pool, scratch_pool)); 12509#if 0 /* ### TODO: Also handle options for directories! */ 12510 else if (id == svn_client_conflict_option_both_moved_dir_merge) 12511 { 12512 } 12513 else if (id == svn_client_conflict_option_both_moved_dir_move_merge) 12514 { 12515 } 12516#endif 12517 else 12518 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12519 _("Unexpected option id '%d'"), id); 12520 } 12521 else 12522 { 12523 struct conflict_tree_incoming_delete_details *details; 12524 apr_array_header_t *move_target_wc_abspaths; 12525 const char *moved_to_abspath; 12526 12527 details = conflict->tree_conflict_incoming_details; 12528 if (details == NULL || details->wc_move_targets == NULL) 12529 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 12530 _("Setting a move target requires details " 12531 "for tree conflict at '%s' to be fetched " 12532 "from the repository first"), 12533 svn_dirent_local_style(victim_abspath, 12534 scratch_pool)); 12535 12536 move_target_wc_abspaths = 12537 svn_hash_gets(details->wc_move_targets, 12538 get_moved_to_repos_relpath(details, scratch_pool)); 12539 12540 if (preferred_move_target_idx < 0 || 12541 preferred_move_target_idx > move_target_wc_abspaths->nelts) 12542 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 12543 _("Index '%d' is out of bounds of the possible " 12544 "move target list for '%s'"), 12545 preferred_move_target_idx, 12546 svn_dirent_local_style(victim_abspath, 12547 scratch_pool)); 12548 12549 /* Record the user's preference. */ 12550 details->wc_move_target_idx = preferred_move_target_idx; 12551 12552 /* Update option description. */ 12553 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, 12554 details->wc_move_target_idx, 12555 const char *); 12556 SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description, 12557 conflict, ctx, 12558 moved_to_abspath, 12559 conflict->pool, 12560 scratch_pool)); 12561 } 12562 return SVN_NO_ERROR; 12563} 12564 12565svn_error_t * 12566svn_client_conflict_option_set_moved_to_abspath( 12567 svn_client_conflict_option_t *option, 12568 int preferred_move_target_idx, 12569 svn_client_ctx_t *ctx, 12570 apr_pool_t *scratch_pool) 12571{ 12572 /* The only difference to API version 2 is an assertion failure if 12573 * an unexpected option is passed. 12574 * We do not emulate this old behaviour since clients written against 12575 * the previous API will just keep working. */ 12576 return svn_error_trace( 12577 svn_client_conflict_option_set_moved_to_abspath2(option, 12578 preferred_move_target_idx, ctx, scratch_pool)); 12579} 12580 12581svn_error_t * 12582svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options, 12583 svn_client_conflict_t *conflict, 12584 svn_client_ctx_t *ctx, 12585 apr_pool_t *result_pool, 12586 apr_pool_t *scratch_pool) 12587{ 12588 SVN_ERR(assert_tree_conflict(conflict, scratch_pool)); 12589 12590 *options = apr_array_make(result_pool, 2, 12591 sizeof(svn_client_conflict_option_t *)); 12592 12593 /* Add postpone option. */ 12594 add_resolution_option(*options, conflict, 12595 svn_client_conflict_option_postpone, 12596 _("Postpone"), 12597 _("skip this conflict and leave it unresolved"), 12598 resolve_postpone); 12599 12600 /* Add an option which marks the conflict resolved. */ 12601 SVN_ERR(configure_option_accept_current_wc_state(conflict, *options)); 12602 12603 /* Configure options which offer automatic resolution. */ 12604 SVN_ERR(configure_option_update_move_destination(conflict, *options)); 12605 SVN_ERR(configure_option_update_raise_moved_away_children(conflict, 12606 *options)); 12607 SVN_ERR(configure_option_incoming_add_ignore(conflict, ctx, *options, 12608 scratch_pool)); 12609 SVN_ERR(configure_option_incoming_added_file_text_merge(conflict, ctx, 12610 *options, 12611 scratch_pool)); 12612 SVN_ERR(configure_option_incoming_added_file_replace_and_merge(conflict, 12613 ctx, 12614 *options, 12615 scratch_pool)); 12616 SVN_ERR(configure_option_incoming_added_dir_merge(conflict, ctx, 12617 *options, 12618 scratch_pool)); 12619 SVN_ERR(configure_option_incoming_added_dir_replace(conflict, ctx, 12620 *options, 12621 scratch_pool)); 12622 SVN_ERR(configure_option_incoming_added_dir_replace_and_merge(conflict, 12623 ctx, 12624 *options, 12625 scratch_pool)); 12626 SVN_ERR(configure_option_incoming_delete_ignore(conflict, ctx, *options, 12627 scratch_pool)); 12628 SVN_ERR(configure_option_incoming_delete_accept(conflict, ctx, *options, 12629 scratch_pool)); 12630 SVN_ERR(configure_option_incoming_move_file_merge(conflict, ctx, *options, 12631 scratch_pool)); 12632 SVN_ERR(configure_option_incoming_dir_merge(conflict, ctx, *options, 12633 scratch_pool)); 12634 SVN_ERR(configure_option_local_move_file_or_dir_merge(conflict, ctx, 12635 *options, 12636 scratch_pool)); 12637 SVN_ERR(configure_option_sibling_move_merge(conflict, ctx, *options, 12638 scratch_pool)); 12639 SVN_ERR(configure_option_both_moved_file_merge(conflict, ctx, *options, 12640 scratch_pool)); 12641 SVN_ERR(configure_option_both_moved_dir_merge(conflict, ctx, *options, 12642 scratch_pool)); 12643 12644 return SVN_NO_ERROR; 12645} 12646 12647/* Swallow authz failures and return SVN_NO_ERROR in that case. 12648 * Otherwise, return ERR unchanged. */ 12649static svn_error_t * 12650ignore_authz_failures(svn_error_t *err) 12651{ 12652 if (err && ( svn_error_find_cause(err, SVN_ERR_AUTHZ_UNREADABLE) 12653 || svn_error_find_cause(err, SVN_ERR_RA_NOT_AUTHORIZED) 12654 || svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN))) 12655 { 12656 svn_error_clear(err); 12657 err = SVN_NO_ERROR; 12658 } 12659 12660 return err; 12661} 12662 12663svn_error_t * 12664svn_client_conflict_tree_get_details(svn_client_conflict_t *conflict, 12665 svn_client_ctx_t *ctx, 12666 apr_pool_t *scratch_pool) 12667{ 12668 SVN_ERR(assert_tree_conflict(conflict, scratch_pool)); 12669 12670 if (ctx->notify_func2) 12671 { 12672 svn_wc_notify_t *notify; 12673 12674 notify = svn_wc_create_notify( 12675 svn_client_conflict_get_local_abspath(conflict), 12676 svn_wc_notify_begin_search_tree_conflict_details, 12677 scratch_pool), 12678 ctx->notify_func2(ctx->notify_baton2, notify, 12679 scratch_pool); 12680 } 12681 12682 /* Collecting conflict details may fail due to insufficient access rights. 12683 * This is not a failure but simply restricts our future options. */ 12684 if (conflict->tree_conflict_get_incoming_details_func) 12685 SVN_ERR(ignore_authz_failures( 12686 conflict->tree_conflict_get_incoming_details_func(conflict, ctx, 12687 scratch_pool))); 12688 12689 12690 if (conflict->tree_conflict_get_local_details_func) 12691 SVN_ERR(ignore_authz_failures( 12692 conflict->tree_conflict_get_local_details_func(conflict, ctx, 12693 scratch_pool))); 12694 12695 if (ctx->notify_func2) 12696 { 12697 svn_wc_notify_t *notify; 12698 12699 notify = svn_wc_create_notify( 12700 svn_client_conflict_get_local_abspath(conflict), 12701 svn_wc_notify_end_search_tree_conflict_details, 12702 scratch_pool), 12703 ctx->notify_func2(ctx->notify_baton2, notify, 12704 scratch_pool); 12705 } 12706 12707 return SVN_NO_ERROR; 12708} 12709 12710svn_client_conflict_option_id_t 12711svn_client_conflict_option_get_id(svn_client_conflict_option_t *option) 12712{ 12713 return option->id; 12714} 12715 12716const char * 12717svn_client_conflict_option_get_label(svn_client_conflict_option_t *option, 12718 apr_pool_t *result_pool) 12719{ 12720 return apr_pstrdup(result_pool, option->label); 12721} 12722 12723const char * 12724svn_client_conflict_option_get_description(svn_client_conflict_option_t *option, 12725 apr_pool_t *result_pool) 12726{ 12727 return apr_pstrdup(result_pool, option->description); 12728} 12729 12730svn_client_conflict_option_id_t 12731svn_client_conflict_get_recommended_option_id(svn_client_conflict_t *conflict) 12732{ 12733 return conflict->recommended_option_id; 12734} 12735 12736svn_error_t * 12737svn_client_conflict_text_resolve(svn_client_conflict_t *conflict, 12738 svn_client_conflict_option_t *option, 12739 svn_client_ctx_t *ctx, 12740 apr_pool_t *scratch_pool) 12741{ 12742 SVN_ERR(assert_text_conflict(conflict, scratch_pool)); 12743 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool)); 12744 12745 return SVN_NO_ERROR; 12746} 12747 12748svn_client_conflict_option_t * 12749svn_client_conflict_option_find_by_id(apr_array_header_t *options, 12750 svn_client_conflict_option_id_t option_id) 12751{ 12752 int i; 12753 12754 for (i = 0; i < options->nelts; i++) 12755 { 12756 svn_client_conflict_option_t *this_option; 12757 svn_client_conflict_option_id_t this_option_id; 12758 12759 this_option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); 12760 this_option_id = svn_client_conflict_option_get_id(this_option); 12761 12762 if (this_option_id == option_id) 12763 return this_option; 12764 } 12765 12766 return NULL; 12767} 12768 12769svn_error_t * 12770svn_client_conflict_text_resolve_by_id( 12771 svn_client_conflict_t *conflict, 12772 svn_client_conflict_option_id_t option_id, 12773 svn_client_ctx_t *ctx, 12774 apr_pool_t *scratch_pool) 12775{ 12776 apr_array_header_t *resolution_options; 12777 svn_client_conflict_option_t *option; 12778 12779 SVN_ERR(svn_client_conflict_text_get_resolution_options( 12780 &resolution_options, conflict, ctx, 12781 scratch_pool, scratch_pool)); 12782 option = svn_client_conflict_option_find_by_id(resolution_options, 12783 option_id); 12784 if (option == NULL) 12785 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE, 12786 NULL, 12787 _("Inapplicable conflict resolution option " 12788 "given for conflicted path '%s'"), 12789 svn_dirent_local_style(conflict->local_abspath, 12790 scratch_pool)); 12791 12792 SVN_ERR(svn_client_conflict_text_resolve(conflict, option, ctx, scratch_pool)); 12793 12794 return SVN_NO_ERROR; 12795} 12796 12797svn_client_conflict_option_id_t 12798svn_client_conflict_text_get_resolution(svn_client_conflict_t *conflict) 12799{ 12800 return conflict->resolution_text; 12801} 12802 12803svn_error_t * 12804svn_client_conflict_prop_resolve(svn_client_conflict_t *conflict, 12805 const char *propname, 12806 svn_client_conflict_option_t *option, 12807 svn_client_ctx_t *ctx, 12808 apr_pool_t *scratch_pool) 12809{ 12810 SVN_ERR(assert_prop_conflict(conflict, scratch_pool)); 12811 option->type_data.prop.propname = propname; 12812 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool)); 12813 12814 return SVN_NO_ERROR; 12815} 12816 12817svn_error_t * 12818svn_client_conflict_prop_resolve_by_id( 12819 svn_client_conflict_t *conflict, 12820 const char *propname, 12821 svn_client_conflict_option_id_t option_id, 12822 svn_client_ctx_t *ctx, 12823 apr_pool_t *scratch_pool) 12824{ 12825 apr_array_header_t *resolution_options; 12826 svn_client_conflict_option_t *option; 12827 12828 SVN_ERR(svn_client_conflict_prop_get_resolution_options( 12829 &resolution_options, conflict, ctx, 12830 scratch_pool, scratch_pool)); 12831 option = svn_client_conflict_option_find_by_id(resolution_options, 12832 option_id); 12833 if (option == NULL) 12834 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE, 12835 NULL, 12836 _("Inapplicable conflict resolution option " 12837 "given for conflicted path '%s'"), 12838 svn_dirent_local_style(conflict->local_abspath, 12839 scratch_pool)); 12840 SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option, ctx, 12841 scratch_pool)); 12842 12843 return SVN_NO_ERROR; 12844} 12845 12846svn_client_conflict_option_id_t 12847svn_client_conflict_prop_get_resolution(svn_client_conflict_t *conflict, 12848 const char *propname) 12849{ 12850 svn_client_conflict_option_t *option; 12851 12852 option = svn_hash_gets(conflict->resolved_props, propname); 12853 if (option == NULL) 12854 return svn_client_conflict_option_unspecified; 12855 12856 return svn_client_conflict_option_get_id(option); 12857} 12858 12859svn_error_t * 12860svn_client_conflict_tree_resolve(svn_client_conflict_t *conflict, 12861 svn_client_conflict_option_t *option, 12862 svn_client_ctx_t *ctx, 12863 apr_pool_t *scratch_pool) 12864{ 12865 SVN_ERR(assert_tree_conflict(conflict, scratch_pool)); 12866 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool)); 12867 12868 return SVN_NO_ERROR; 12869} 12870 12871svn_error_t * 12872svn_client_conflict_tree_resolve_by_id( 12873 svn_client_conflict_t *conflict, 12874 svn_client_conflict_option_id_t option_id, 12875 svn_client_ctx_t *ctx, 12876 apr_pool_t *scratch_pool) 12877{ 12878 apr_array_header_t *resolution_options; 12879 svn_client_conflict_option_t *option; 12880 12881 SVN_ERR(svn_client_conflict_tree_get_resolution_options( 12882 &resolution_options, conflict, ctx, 12883 scratch_pool, scratch_pool)); 12884 option = svn_client_conflict_option_find_by_id(resolution_options, 12885 option_id); 12886 if (option == NULL) 12887 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE, 12888 NULL, 12889 _("Inapplicable conflict resolution option " 12890 "given for conflicted path '%s'"), 12891 svn_dirent_local_style(conflict->local_abspath, 12892 scratch_pool)); 12893 SVN_ERR(svn_client_conflict_tree_resolve(conflict, option, ctx, scratch_pool)); 12894 12895 return SVN_NO_ERROR; 12896} 12897 12898svn_client_conflict_option_id_t 12899svn_client_conflict_tree_get_resolution(svn_client_conflict_t *conflict) 12900{ 12901 return conflict->resolution_tree; 12902} 12903 12904/* Return the legacy conflict descriptor which is wrapped by CONFLICT. */ 12905static const svn_wc_conflict_description2_t * 12906get_conflict_desc2_t(svn_client_conflict_t *conflict) 12907{ 12908 if (conflict->legacy_text_conflict) 12909 return conflict->legacy_text_conflict; 12910 12911 if (conflict->legacy_tree_conflict) 12912 return conflict->legacy_tree_conflict; 12913 12914 if (conflict->prop_conflicts && conflict->legacy_prop_conflict_propname) 12915 return svn_hash_gets(conflict->prop_conflicts, 12916 conflict->legacy_prop_conflict_propname); 12917 12918 return NULL; 12919} 12920 12921svn_error_t * 12922svn_client_conflict_get_conflicted(svn_boolean_t *text_conflicted, 12923 apr_array_header_t **props_conflicted, 12924 svn_boolean_t *tree_conflicted, 12925 svn_client_conflict_t *conflict, 12926 apr_pool_t *result_pool, 12927 apr_pool_t *scratch_pool) 12928{ 12929 if (text_conflicted) 12930 *text_conflicted = (conflict->legacy_text_conflict != NULL); 12931 12932 if (props_conflicted) 12933 { 12934 if (conflict->prop_conflicts) 12935 SVN_ERR(svn_hash_keys(props_conflicted, conflict->prop_conflicts, 12936 result_pool)); 12937 else 12938 *props_conflicted = apr_array_make(result_pool, 0, 12939 sizeof(const char*)); 12940 } 12941 12942 if (tree_conflicted) 12943 *tree_conflicted = (conflict->legacy_tree_conflict != NULL); 12944 12945 return SVN_NO_ERROR; 12946} 12947 12948const char * 12949svn_client_conflict_get_local_abspath(svn_client_conflict_t *conflict) 12950{ 12951 return conflict->local_abspath; 12952} 12953 12954svn_wc_operation_t 12955svn_client_conflict_get_operation(svn_client_conflict_t *conflict) 12956{ 12957 return get_conflict_desc2_t(conflict)->operation; 12958} 12959 12960svn_wc_conflict_action_t 12961svn_client_conflict_get_incoming_change(svn_client_conflict_t *conflict) 12962{ 12963 return get_conflict_desc2_t(conflict)->action; 12964} 12965 12966svn_wc_conflict_reason_t 12967svn_client_conflict_get_local_change(svn_client_conflict_t *conflict) 12968{ 12969 return get_conflict_desc2_t(conflict)->reason; 12970} 12971 12972svn_error_t * 12973svn_client_conflict_get_repos_info(const char **repos_root_url, 12974 const char **repos_uuid, 12975 svn_client_conflict_t *conflict, 12976 apr_pool_t *result_pool, 12977 apr_pool_t *scratch_pool) 12978{ 12979 if (repos_root_url) 12980 { 12981 if (get_conflict_desc2_t(conflict)->src_left_version) 12982 *repos_root_url = 12983 get_conflict_desc2_t(conflict)->src_left_version->repos_url; 12984 else if (get_conflict_desc2_t(conflict)->src_right_version) 12985 *repos_root_url = 12986 get_conflict_desc2_t(conflict)->src_right_version->repos_url; 12987 else 12988 *repos_root_url = NULL; 12989 } 12990 12991 if (repos_uuid) 12992 { 12993 if (get_conflict_desc2_t(conflict)->src_left_version) 12994 *repos_uuid = 12995 get_conflict_desc2_t(conflict)->src_left_version->repos_uuid; 12996 else if (get_conflict_desc2_t(conflict)->src_right_version) 12997 *repos_uuid = 12998 get_conflict_desc2_t(conflict)->src_right_version->repos_uuid; 12999 else 13000 *repos_uuid = NULL; 13001 } 13002 13003 return SVN_NO_ERROR; 13004} 13005 13006svn_error_t * 13007svn_client_conflict_get_incoming_old_repos_location( 13008 const char **incoming_old_repos_relpath, 13009 svn_revnum_t *incoming_old_pegrev, 13010 svn_node_kind_t *incoming_old_node_kind, 13011 svn_client_conflict_t *conflict, 13012 apr_pool_t *result_pool, 13013 apr_pool_t *scratch_pool) 13014{ 13015 if (incoming_old_repos_relpath) 13016 { 13017 if (get_conflict_desc2_t(conflict)->src_left_version) 13018 *incoming_old_repos_relpath = 13019 get_conflict_desc2_t(conflict)->src_left_version->path_in_repos; 13020 else 13021 *incoming_old_repos_relpath = NULL; 13022 } 13023 13024 if (incoming_old_pegrev) 13025 { 13026 if (get_conflict_desc2_t(conflict)->src_left_version) 13027 *incoming_old_pegrev = 13028 get_conflict_desc2_t(conflict)->src_left_version->peg_rev; 13029 else 13030 *incoming_old_pegrev = SVN_INVALID_REVNUM; 13031 } 13032 13033 if (incoming_old_node_kind) 13034 { 13035 if (get_conflict_desc2_t(conflict)->src_left_version) 13036 *incoming_old_node_kind = 13037 get_conflict_desc2_t(conflict)->src_left_version->node_kind; 13038 else 13039 *incoming_old_node_kind = svn_node_none; 13040 } 13041 13042 return SVN_NO_ERROR; 13043} 13044 13045svn_error_t * 13046svn_client_conflict_get_incoming_new_repos_location( 13047 const char **incoming_new_repos_relpath, 13048 svn_revnum_t *incoming_new_pegrev, 13049 svn_node_kind_t *incoming_new_node_kind, 13050 svn_client_conflict_t *conflict, 13051 apr_pool_t *result_pool, 13052 apr_pool_t *scratch_pool) 13053{ 13054 if (incoming_new_repos_relpath) 13055 { 13056 if (get_conflict_desc2_t(conflict)->src_right_version) 13057 *incoming_new_repos_relpath = 13058 get_conflict_desc2_t(conflict)->src_right_version->path_in_repos; 13059 else 13060 *incoming_new_repos_relpath = NULL; 13061 } 13062 13063 if (incoming_new_pegrev) 13064 { 13065 if (get_conflict_desc2_t(conflict)->src_right_version) 13066 *incoming_new_pegrev = 13067 get_conflict_desc2_t(conflict)->src_right_version->peg_rev; 13068 else 13069 *incoming_new_pegrev = SVN_INVALID_REVNUM; 13070 } 13071 13072 if (incoming_new_node_kind) 13073 { 13074 if (get_conflict_desc2_t(conflict)->src_right_version) 13075 *incoming_new_node_kind = 13076 get_conflict_desc2_t(conflict)->src_right_version->node_kind; 13077 else 13078 *incoming_new_node_kind = svn_node_none; 13079 } 13080 13081 return SVN_NO_ERROR; 13082} 13083 13084svn_node_kind_t 13085svn_client_conflict_tree_get_victim_node_kind(svn_client_conflict_t *conflict) 13086{ 13087 SVN_ERR_ASSERT_NO_RETURN(assert_tree_conflict(conflict, conflict->pool) 13088 == SVN_NO_ERROR); 13089 13090 return get_conflict_desc2_t(conflict)->node_kind; 13091} 13092 13093svn_error_t * 13094svn_client_conflict_prop_get_propvals(const svn_string_t **base_propval, 13095 const svn_string_t **working_propval, 13096 const svn_string_t **incoming_old_propval, 13097 const svn_string_t **incoming_new_propval, 13098 svn_client_conflict_t *conflict, 13099 const char *propname, 13100 apr_pool_t *result_pool) 13101{ 13102 const svn_wc_conflict_description2_t *desc; 13103 13104 SVN_ERR(assert_prop_conflict(conflict, conflict->pool)); 13105 13106 desc = svn_hash_gets(conflict->prop_conflicts, propname); 13107 if (desc == NULL) 13108 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 13109 _("Property '%s' is not in conflict."), propname); 13110 13111 if (base_propval) 13112 *base_propval = 13113 svn_string_dup(desc->prop_value_base, result_pool); 13114 13115 if (working_propval) 13116 *working_propval = 13117 svn_string_dup(desc->prop_value_working, result_pool); 13118 13119 if (incoming_old_propval) 13120 *incoming_old_propval = 13121 svn_string_dup(desc->prop_value_incoming_old, result_pool); 13122 13123 if (incoming_new_propval) 13124 *incoming_new_propval = 13125 svn_string_dup(desc->prop_value_incoming_new, result_pool); 13126 13127 return SVN_NO_ERROR; 13128} 13129 13130const char * 13131svn_client_conflict_prop_get_reject_abspath(svn_client_conflict_t *conflict) 13132{ 13133 SVN_ERR_ASSERT_NO_RETURN(assert_prop_conflict(conflict, conflict->pool) 13134 == SVN_NO_ERROR); 13135 13136 /* svn_wc_conflict_description2_t stores this path in 'their_abspath' */ 13137 return get_conflict_desc2_t(conflict)->their_abspath; 13138} 13139 13140const char * 13141svn_client_conflict_text_get_mime_type(svn_client_conflict_t *conflict) 13142{ 13143 SVN_ERR_ASSERT_NO_RETURN(assert_text_conflict(conflict, conflict->pool) 13144 == SVN_NO_ERROR); 13145 13146 return get_conflict_desc2_t(conflict)->mime_type; 13147} 13148 13149svn_error_t * 13150svn_client_conflict_text_get_contents(const char **base_abspath, 13151 const char **working_abspath, 13152 const char **incoming_old_abspath, 13153 const char **incoming_new_abspath, 13154 svn_client_conflict_t *conflict, 13155 apr_pool_t *result_pool, 13156 apr_pool_t *scratch_pool) 13157{ 13158 SVN_ERR(assert_text_conflict(conflict, scratch_pool)); 13159 13160 if (base_abspath) 13161 { 13162 if (svn_client_conflict_get_operation(conflict) == 13163 svn_wc_operation_merge) 13164 *base_abspath = NULL; /* ### WC base contents not available yet */ 13165 else /* update/switch */ 13166 *base_abspath = get_conflict_desc2_t(conflict)->base_abspath; 13167 } 13168 13169 if (working_abspath) 13170 *working_abspath = get_conflict_desc2_t(conflict)->my_abspath; 13171 13172 if (incoming_old_abspath) 13173 *incoming_old_abspath = get_conflict_desc2_t(conflict)->base_abspath; 13174 13175 if (incoming_new_abspath) 13176 *incoming_new_abspath = get_conflict_desc2_t(conflict)->their_abspath; 13177 13178 return SVN_NO_ERROR; 13179} 13180 13181/* Set up type-specific data for a new conflict object. */ 13182static svn_error_t * 13183conflict_type_specific_setup(svn_client_conflict_t *conflict, 13184 apr_pool_t *scratch_pool) 13185{ 13186 svn_boolean_t tree_conflicted; 13187 svn_wc_operation_t operation; 13188 svn_wc_conflict_action_t incoming_change; 13189 svn_wc_conflict_reason_t local_change; 13190 13191 /* For now, we only deal with tree conflicts here. */ 13192 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted, 13193 conflict, scratch_pool, 13194 scratch_pool)); 13195 if (!tree_conflicted) 13196 return SVN_NO_ERROR; 13197 13198 /* Set a default description function. */ 13199 conflict->tree_conflict_get_incoming_description_func = 13200 conflict_tree_get_incoming_description_generic; 13201 conflict->tree_conflict_get_local_description_func = 13202 conflict_tree_get_local_description_generic; 13203 13204 operation = svn_client_conflict_get_operation(conflict); 13205 incoming_change = svn_client_conflict_get_incoming_change(conflict); 13206 local_change = svn_client_conflict_get_local_change(conflict); 13207 13208 /* Set type-specific description and details functions. */ 13209 if (incoming_change == svn_wc_conflict_action_delete || 13210 incoming_change == svn_wc_conflict_action_replace) 13211 { 13212 conflict->tree_conflict_get_incoming_description_func = 13213 conflict_tree_get_description_incoming_delete; 13214 conflict->tree_conflict_get_incoming_details_func = 13215 conflict_tree_get_details_incoming_delete; 13216 } 13217 else if (incoming_change == svn_wc_conflict_action_add) 13218 { 13219 conflict->tree_conflict_get_incoming_description_func = 13220 conflict_tree_get_description_incoming_add; 13221 conflict->tree_conflict_get_incoming_details_func = 13222 conflict_tree_get_details_incoming_add; 13223 } 13224 else if (incoming_change == svn_wc_conflict_action_edit) 13225 { 13226 conflict->tree_conflict_get_incoming_description_func = 13227 conflict_tree_get_description_incoming_edit; 13228 conflict->tree_conflict_get_incoming_details_func = 13229 conflict_tree_get_details_incoming_edit; 13230 } 13231 13232 if (local_change == svn_wc_conflict_reason_missing) 13233 { 13234 conflict->tree_conflict_get_local_description_func = 13235 conflict_tree_get_description_local_missing; 13236 conflict->tree_conflict_get_local_details_func = 13237 conflict_tree_get_details_local_missing; 13238 } 13239 else if (local_change == svn_wc_conflict_reason_moved_away && 13240 operation == svn_wc_operation_update /* ### what about switch? */) 13241 { 13242 conflict->tree_conflict_get_local_details_func = 13243 conflict_tree_get_details_update_local_moved_away; 13244 } 13245 13246 return SVN_NO_ERROR; 13247} 13248 13249svn_error_t * 13250svn_client_conflict_get(svn_client_conflict_t **conflict, 13251 const char *local_abspath, 13252 svn_client_ctx_t *ctx, 13253 apr_pool_t *result_pool, 13254 apr_pool_t *scratch_pool) 13255{ 13256 const apr_array_header_t *descs; 13257 int i; 13258 13259 *conflict = apr_pcalloc(result_pool, sizeof(**conflict)); 13260 13261 (*conflict)->local_abspath = apr_pstrdup(result_pool, local_abspath); 13262 (*conflict)->resolution_text = svn_client_conflict_option_unspecified; 13263 (*conflict)->resolution_tree = svn_client_conflict_option_unspecified; 13264 (*conflict)->resolved_props = apr_hash_make(result_pool); 13265 (*conflict)->recommended_option_id = svn_client_conflict_option_unspecified; 13266 (*conflict)->pool = result_pool; 13267 13268 /* Add all legacy conflict descriptors we can find. Eventually, this code 13269 * path should stop relying on svn_wc_conflict_description2_t entirely. */ 13270 SVN_ERR(svn_wc__read_conflict_descriptions2_t(&descs, ctx->wc_ctx, 13271 local_abspath, 13272 result_pool, scratch_pool)); 13273 for (i = 0; i < descs->nelts; i++) 13274 { 13275 const svn_wc_conflict_description2_t *desc; 13276 13277 desc = APR_ARRAY_IDX(descs, i, const svn_wc_conflict_description2_t *); 13278 add_legacy_desc_to_conflict(desc, *conflict, result_pool); 13279 } 13280 13281 SVN_ERR(conflict_type_specific_setup(*conflict, scratch_pool)); 13282 13283 return SVN_NO_ERROR; 13284} 13285 13286/* Baton for conflict_status_walker */ 13287struct conflict_status_walker_baton 13288{ 13289 svn_client_conflict_walk_func_t conflict_walk_func; 13290 void *conflict_walk_func_baton; 13291 svn_client_ctx_t *ctx; 13292 svn_wc_notify_func2_t notify_func; 13293 void *notify_baton; 13294 svn_boolean_t resolved_a_tree_conflict; 13295 apr_hash_t *unresolved_tree_conflicts; 13296}; 13297 13298/* Implements svn_wc_notify_func2_t to collect new conflicts caused by 13299 resolving a tree conflict. */ 13300static void 13301tree_conflict_collector(void *baton, 13302 const svn_wc_notify_t *notify, 13303 apr_pool_t *pool) 13304{ 13305 struct conflict_status_walker_baton *cswb = baton; 13306 13307 if (cswb->notify_func) 13308 cswb->notify_func(cswb->notify_baton, notify, pool); 13309 13310 if (cswb->unresolved_tree_conflicts 13311 && (notify->action == svn_wc_notify_tree_conflict 13312 || notify->prop_state == svn_wc_notify_state_conflicted 13313 || notify->content_state == svn_wc_notify_state_conflicted)) 13314 { 13315 if (!svn_hash_gets(cswb->unresolved_tree_conflicts, notify->path)) 13316 { 13317 const char *tc_abspath; 13318 apr_pool_t *hash_pool; 13319 13320 hash_pool = apr_hash_pool_get(cswb->unresolved_tree_conflicts); 13321 tc_abspath = apr_pstrdup(hash_pool, notify->path); 13322 svn_hash_sets(cswb->unresolved_tree_conflicts, tc_abspath, ""); 13323 } 13324 } 13325} 13326 13327/* 13328 * Record a tree conflict resolution failure due to error condition ERR 13329 * in the RESOLVE_LATER hash table. If the hash table is not available 13330 * (meaning the caller does not wish to retry resolution later), or if 13331 * the error condition does not indicate circumstances where another 13332 * existing tree conflict is blocking the resolution attempt, then 13333 * return the error ERR itself. 13334 */ 13335static svn_error_t * 13336handle_tree_conflict_resolution_failure(const char *local_abspath, 13337 svn_error_t *err, 13338 apr_hash_t *unresolved_tree_conflicts) 13339{ 13340 const char *tc_abspath; 13341 13342 if (!unresolved_tree_conflicts 13343 || (err->apr_err != SVN_ERR_WC_OBSTRUCTED_UPDATE 13344 && err->apr_err != SVN_ERR_WC_FOUND_CONFLICT)) 13345 return svn_error_trace(err); /* Give up. Do not retry resolution later. */ 13346 13347 svn_error_clear(err); 13348 tc_abspath = apr_pstrdup(apr_hash_pool_get(unresolved_tree_conflicts), 13349 local_abspath); 13350 13351 svn_hash_sets(unresolved_tree_conflicts, tc_abspath, ""); 13352 13353 return SVN_NO_ERROR; /* Caller may retry after resolving other conflicts. */ 13354} 13355 13356/* Implements svn_wc_status4_t to walk all conflicts to resolve. 13357 */ 13358static svn_error_t * 13359conflict_status_walker(void *baton, 13360 const char *local_abspath, 13361 const svn_wc_status3_t *status, 13362 apr_pool_t *scratch_pool) 13363{ 13364 struct conflict_status_walker_baton *cswb = baton; 13365 svn_client_conflict_t *conflict; 13366 svn_error_t *err; 13367 svn_boolean_t tree_conflicted; 13368 13369 if (!status->conflicted) 13370 return SVN_NO_ERROR; 13371 13372 SVN_ERR(svn_client_conflict_get(&conflict, local_abspath, cswb->ctx, 13373 scratch_pool, scratch_pool)); 13374 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted, 13375 conflict, scratch_pool, 13376 scratch_pool)); 13377 err = cswb->conflict_walk_func(cswb->conflict_walk_func_baton, 13378 conflict, scratch_pool); 13379 if (err) 13380 { 13381 if (tree_conflicted) 13382 SVN_ERR(handle_tree_conflict_resolution_failure( 13383 local_abspath, err, cswb->unresolved_tree_conflicts)); 13384 13385 else 13386 return svn_error_trace(err); 13387 } 13388 13389 if (tree_conflicted) 13390 { 13391 svn_client_conflict_option_id_t resolution; 13392 13393 resolution = svn_client_conflict_tree_get_resolution(conflict); 13394 if (resolution != svn_client_conflict_option_unspecified && 13395 resolution != svn_client_conflict_option_postpone) 13396 cswb->resolved_a_tree_conflict = TRUE; 13397 } 13398 13399 return SVN_NO_ERROR; 13400} 13401 13402svn_error_t * 13403svn_client_conflict_walk(const char *local_abspath, 13404 svn_depth_t depth, 13405 svn_client_conflict_walk_func_t conflict_walk_func, 13406 void *conflict_walk_func_baton, 13407 svn_client_ctx_t *ctx, 13408 apr_pool_t *scratch_pool) 13409{ 13410 struct conflict_status_walker_baton cswb; 13411 apr_pool_t *iterpool = NULL; 13412 svn_error_t *err = SVN_NO_ERROR; 13413 13414 if (depth == svn_depth_unknown) 13415 depth = svn_depth_infinity; 13416 13417 cswb.conflict_walk_func = conflict_walk_func; 13418 cswb.conflict_walk_func_baton = conflict_walk_func_baton; 13419 cswb.ctx = ctx; 13420 cswb.resolved_a_tree_conflict = FALSE; 13421 cswb.unresolved_tree_conflicts = apr_hash_make(scratch_pool); 13422 13423 if (ctx->notify_func2) 13424 ctx->notify_func2(ctx->notify_baton2, 13425 svn_wc_create_notify( 13426 local_abspath, 13427 svn_wc_notify_conflict_resolver_starting, 13428 scratch_pool), 13429 scratch_pool); 13430 13431 /* Swap in our notify_func wrapper. We must revert this before returning! */ 13432 cswb.notify_func = ctx->notify_func2; 13433 cswb.notify_baton = ctx->notify_baton2; 13434 ctx->notify_func2 = tree_conflict_collector; 13435 ctx->notify_baton2 = &cswb; 13436 13437 err = svn_wc_walk_status(ctx->wc_ctx, 13438 local_abspath, 13439 depth, 13440 FALSE /* get_all */, 13441 FALSE /* no_ignore */, 13442 TRUE /* ignore_text_mods */, 13443 NULL /* ignore_patterns */, 13444 conflict_status_walker, &cswb, 13445 ctx->cancel_func, ctx->cancel_baton, 13446 scratch_pool); 13447 13448 /* If we got new tree conflicts (or delayed conflicts) during the initial 13449 walk, we now walk them one by one as closure. */ 13450 while (!err && cswb.unresolved_tree_conflicts && 13451 apr_hash_count(cswb.unresolved_tree_conflicts)) 13452 { 13453 apr_hash_index_t *hi; 13454 svn_wc_status3_t *status = NULL; 13455 const char *tc_abspath = NULL; 13456 13457 if (iterpool) 13458 svn_pool_clear(iterpool); 13459 else 13460 iterpool = svn_pool_create(scratch_pool); 13461 13462 hi = apr_hash_first(scratch_pool, cswb.unresolved_tree_conflicts); 13463 cswb.unresolved_tree_conflicts = apr_hash_make(scratch_pool); 13464 cswb.resolved_a_tree_conflict = FALSE; 13465 13466 for (; hi && !err; hi = apr_hash_next(hi)) 13467 { 13468 svn_pool_clear(iterpool); 13469 13470 tc_abspath = apr_hash_this_key(hi); 13471 13472 if (ctx->cancel_func) 13473 { 13474 err = ctx->cancel_func(ctx->cancel_baton); 13475 if (err) 13476 break; 13477 } 13478 13479 err = svn_error_trace(svn_wc_status3(&status, ctx->wc_ctx, 13480 tc_abspath, 13481 iterpool, iterpool)); 13482 if (err) 13483 break; 13484 13485 err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath, 13486 status, scratch_pool)); 13487 if (err) 13488 break; 13489 } 13490 13491 if (!err && !cswb.resolved_a_tree_conflict && tc_abspath && 13492 apr_hash_count(cswb.unresolved_tree_conflicts)) 13493 { 13494 /* None of the remaining conflicts got resolved, without any error. 13495 * Disable the 'unresolved_tree_conflicts' cache and try again. */ 13496 cswb.unresolved_tree_conflicts = NULL; 13497 13498 /* Run the most recent resolve operation again. 13499 * We still have status and tc_abspath for that one. 13500 * This should uncover the error which prevents resolution. */ 13501 err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath, 13502 status, scratch_pool)); 13503 SVN_ERR_ASSERT(err != NULL); 13504 13505 err = svn_error_createf( 13506 SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err, 13507 _("Unable to resolve pending conflict on '%s'"), 13508 svn_dirent_local_style(tc_abspath, scratch_pool)); 13509 break; 13510 } 13511 } 13512 13513 if (iterpool) 13514 svn_pool_destroy(iterpool); 13515 13516 ctx->notify_func2 = cswb.notify_func; 13517 ctx->notify_baton2 = cswb.notify_baton; 13518 13519 if (!err && ctx->notify_func2) 13520 ctx->notify_func2(ctx->notify_baton2, 13521 svn_wc_create_notify(local_abspath, 13522 svn_wc_notify_conflict_resolver_done, 13523 scratch_pool), 13524 scratch_pool); 13525 13526 return svn_error_trace(err); 13527} 13528