1/* 2 * conflicts.c: routines for managing conflict data. 3 * NOTE: this code doesn't know where the conflict is 4 * actually stored. 5 * 6 * ==================================================================== 7 * Licensed to the Apache Software Foundation (ASF) under one 8 * or more contributor license agreements. See the NOTICE file 9 * distributed with this work for additional information 10 * regarding copyright ownership. The ASF licenses this file 11 * to you under the Apache License, Version 2.0 (the 12 * "License"); you may not use this file except in compliance 13 * with the License. You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, 18 * software distributed under the License is distributed on an 19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 * KIND, either express or implied. See the License for the 21 * specific language governing permissions and limitations 22 * under the License. 23 * ==================================================================== 24 */ 25 26 27 28#include <string.h> 29 30#include <apr_pools.h> 31#include <apr_tables.h> 32#include <apr_hash.h> 33#include <apr_errno.h> 34 35#include "svn_hash.h" 36#include "svn_types.h" 37#include "svn_pools.h" 38#include "svn_string.h" 39#include "svn_error.h" 40#include "svn_dirent_uri.h" 41#include "svn_wc.h" 42#include "svn_io.h" 43#include "svn_diff.h" 44 45#include "wc.h" 46#include "wc_db.h" 47#include "conflicts.h" 48#include "workqueue.h" 49#include "props.h" 50 51#include "private/svn_wc_private.h" 52#include "private/svn_skel.h" 53#include "private/svn_string_private.h" 54 55#include "svn_private_config.h" 56 57/* -------------------------------------------------------------------- 58 * Conflict skel management 59 */ 60 61svn_skel_t * 62svn_wc__conflict_skel_create(apr_pool_t *result_pool) 63{ 64 svn_skel_t *conflict_skel = svn_skel__make_empty_list(result_pool); 65 66 /* Add empty CONFLICTS list */ 67 svn_skel__prepend(svn_skel__make_empty_list(result_pool), conflict_skel); 68 69 /* Add empty WHY list */ 70 svn_skel__prepend(svn_skel__make_empty_list(result_pool), conflict_skel); 71 72 return conflict_skel; 73} 74 75svn_error_t * 76svn_wc__conflict_skel_is_complete(svn_boolean_t *complete, 77 const svn_skel_t *conflict_skel) 78{ 79 *complete = FALSE; 80 81 if (svn_skel__list_length(conflict_skel) < 2) 82 return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, 83 _("Not a conflict skel")); 84 85 if (svn_skel__list_length(conflict_skel->children) < 2) 86 return SVN_NO_ERROR; /* WHY is not set */ 87 88 if (svn_skel__list_length(conflict_skel->children->next) == 0) 89 return SVN_NO_ERROR; /* No conflict set */ 90 91 *complete = TRUE; 92 return SVN_NO_ERROR; 93} 94 95/* Serialize a svn_wc_conflict_version_t before the existing data in skel */ 96static svn_error_t * 97conflict__prepend_location(svn_skel_t *skel, 98 const svn_wc_conflict_version_t *location, 99 svn_boolean_t allow_NULL, 100 apr_pool_t *result_pool, 101 apr_pool_t *scratch_pool) 102{ 103 svn_skel_t *loc; 104 SVN_ERR_ASSERT(location || allow_NULL); 105 106 if (!location) 107 { 108 svn_skel__prepend(svn_skel__make_empty_list(result_pool), skel); 109 return SVN_NO_ERROR; 110 } 111 112 /* ("subversion" repos_root_url repos_uuid repos_relpath rev kind) */ 113 loc = svn_skel__make_empty_list(result_pool); 114 115 svn_skel__prepend_str(svn_node_kind_to_word(location->node_kind), 116 loc, result_pool); 117 118 svn_skel__prepend_int(location->peg_rev, loc, result_pool); 119 120 svn_skel__prepend_str(apr_pstrdup(result_pool, location->path_in_repos), loc, 121 result_pool); 122 123 if (!location->repos_uuid) /* Can theoretically be NULL */ 124 svn_skel__prepend(svn_skel__make_empty_list(result_pool), loc); 125 else 126 svn_skel__prepend_str(location->repos_uuid, loc, result_pool); 127 128 svn_skel__prepend_str(apr_pstrdup(result_pool, location->repos_url), loc, 129 result_pool); 130 131 svn_skel__prepend_str(SVN_WC__CONFLICT_SRC_SUBVERSION, loc, result_pool); 132 133 svn_skel__prepend(loc, skel); 134 return SVN_NO_ERROR; 135} 136 137/* Deserialize a svn_wc_conflict_version_t from the skel. 138 Set *LOCATION to NULL when the data is not a svn_wc_conflict_version_t. */ 139static svn_error_t * 140conflict__read_location(svn_wc_conflict_version_t **location, 141 const svn_skel_t *skel, 142 apr_pool_t *result_pool, 143 apr_pool_t *scratch_pool) 144{ 145 const char *repos_root_url; 146 const char *repos_uuid; 147 const char *repos_relpath; 148 svn_revnum_t revision; 149 apr_int64_t v; 150 svn_node_kind_t node_kind; /* note that 'none' is a legitimate value */ 151 const char *kind_str; 152 153 const svn_skel_t *c = skel->children; 154 155 if (!svn_skel__matches_atom(c, SVN_WC__CONFLICT_SRC_SUBVERSION)) 156 { 157 *location = NULL; 158 return SVN_NO_ERROR; 159 } 160 c = c->next; 161 162 repos_root_url = apr_pstrmemdup(result_pool, c->data, c->len); 163 c = c->next; 164 165 if (c->is_atom) 166 repos_uuid = apr_pstrmemdup(result_pool, c->data, c->len); 167 else 168 repos_uuid = NULL; 169 c = c->next; 170 171 repos_relpath = apr_pstrmemdup(result_pool, c->data, c->len); 172 c = c->next; 173 174 SVN_ERR(svn_skel__parse_int(&v, c, scratch_pool)); 175 revision = (svn_revnum_t)v; 176 c = c->next; 177 178 kind_str = apr_pstrmemdup(scratch_pool, c->data, c->len); 179 node_kind = svn_node_kind_from_word(kind_str); 180 181 *location = svn_wc_conflict_version_create2(repos_root_url, 182 repos_uuid, 183 repos_relpath, 184 revision, 185 node_kind, 186 result_pool); 187 return SVN_NO_ERROR; 188} 189 190/* Get the operation part of CONFLICT_SKELL or NULL if no operation is set 191 at this time */ 192static svn_error_t * 193conflict__get_operation(svn_skel_t **why, 194 const svn_skel_t *conflict_skel) 195{ 196 SVN_ERR_ASSERT(conflict_skel 197 && conflict_skel->children 198 && conflict_skel->children->next 199 && !conflict_skel->children->next->is_atom); 200 201 *why = conflict_skel->children; 202 203 if (!(*why)->children) 204 *why = NULL; /* Operation is not set yet */ 205 206 return SVN_NO_ERROR; 207} 208 209 210svn_error_t * 211svn_wc__conflict_skel_set_op_update(svn_skel_t *conflict_skel, 212 const svn_wc_conflict_version_t *original, 213 const svn_wc_conflict_version_t *target, 214 apr_pool_t *result_pool, 215 apr_pool_t *scratch_pool) 216{ 217 svn_skel_t *why; 218 svn_skel_t *origins; 219 220 SVN_ERR_ASSERT(conflict_skel 221 && conflict_skel->children 222 && conflict_skel->children->next 223 && !conflict_skel->children->next->is_atom); 224 225 SVN_ERR(conflict__get_operation(&why, conflict_skel)); 226 227 SVN_ERR_ASSERT(why == NULL); /* No operation set */ 228 229 why = conflict_skel->children; 230 231 origins = svn_skel__make_empty_list(result_pool); 232 233 SVN_ERR(conflict__prepend_location(origins, target, TRUE, 234 result_pool, scratch_pool)); 235 SVN_ERR(conflict__prepend_location(origins, original, TRUE, 236 result_pool, scratch_pool)); 237 238 svn_skel__prepend(origins, why); 239 svn_skel__prepend_str(SVN_WC__CONFLICT_OP_UPDATE, why, result_pool); 240 241 return SVN_NO_ERROR; 242} 243 244svn_error_t * 245svn_wc__conflict_skel_set_op_switch(svn_skel_t *conflict_skel, 246 const svn_wc_conflict_version_t *original, 247 const svn_wc_conflict_version_t *target, 248 apr_pool_t *result_pool, 249 apr_pool_t *scratch_pool) 250{ 251 svn_skel_t *why; 252 svn_skel_t *origins; 253 254 SVN_ERR_ASSERT(conflict_skel 255 && conflict_skel->children 256 && conflict_skel->children->next 257 && !conflict_skel->children->next->is_atom); 258 259 SVN_ERR(conflict__get_operation(&why, conflict_skel)); 260 261 SVN_ERR_ASSERT(why == NULL); /* No operation set */ 262 263 why = conflict_skel->children; 264 265 origins = svn_skel__make_empty_list(result_pool); 266 267 SVN_ERR(conflict__prepend_location(origins, target, TRUE, 268 result_pool, scratch_pool)); 269 SVN_ERR(conflict__prepend_location(origins, original, TRUE, 270 result_pool, scratch_pool)); 271 272 svn_skel__prepend(origins, why); 273 svn_skel__prepend_str(SVN_WC__CONFLICT_OP_SWITCH, why, result_pool); 274 275 return SVN_NO_ERROR; 276} 277 278svn_error_t * 279svn_wc__conflict_skel_set_op_merge(svn_skel_t *conflict_skel, 280 const svn_wc_conflict_version_t *left, 281 const svn_wc_conflict_version_t *right, 282 apr_pool_t *result_pool, 283 apr_pool_t *scratch_pool) 284{ 285 svn_skel_t *why; 286 svn_skel_t *origins; 287 288 SVN_ERR_ASSERT(conflict_skel 289 && conflict_skel->children 290 && conflict_skel->children->next 291 && !conflict_skel->children->next->is_atom); 292 293 SVN_ERR(conflict__get_operation(&why, conflict_skel)); 294 295 SVN_ERR_ASSERT(why == NULL); /* No operation set */ 296 297 why = conflict_skel->children; 298 299 origins = svn_skel__make_empty_list(result_pool); 300 301 SVN_ERR(conflict__prepend_location(origins, right, TRUE, 302 result_pool, scratch_pool)); 303 304 SVN_ERR(conflict__prepend_location(origins, left, TRUE, 305 result_pool, scratch_pool)); 306 307 svn_skel__prepend(origins, why); 308 svn_skel__prepend_str(SVN_WC__CONFLICT_OP_MERGE, why, result_pool); 309 310 return SVN_NO_ERROR; 311} 312 313/* Gets the conflict data of the specified type CONFLICT_TYPE from 314 CONFLICT_SKEL, or NULL if no such conflict is recorded */ 315static svn_error_t * 316conflict__get_conflict(svn_skel_t **conflict, 317 const svn_skel_t *conflict_skel, 318 const char *conflict_type) 319{ 320 svn_skel_t *c; 321 322 SVN_ERR_ASSERT(conflict_skel 323 && conflict_skel->children 324 && conflict_skel->children->next 325 && !conflict_skel->children->next->is_atom); 326 327 for(c = conflict_skel->children->next->children; 328 c; 329 c = c->next) 330 { 331 if (svn_skel__matches_atom(c->children, conflict_type)) 332 { 333 *conflict = c; 334 return SVN_NO_ERROR; 335 } 336 } 337 338 *conflict = NULL; 339 340 return SVN_NO_ERROR; 341} 342 343svn_error_t * 344svn_wc__conflict_skel_add_text_conflict(svn_skel_t *conflict_skel, 345 svn_wc__db_t *db, 346 const char *wri_abspath, 347 const char *mine_abspath, 348 const char *their_old_abspath, 349 const char *their_abspath, 350 apr_pool_t *result_pool, 351 apr_pool_t *scratch_pool) 352{ 353 svn_skel_t *text_conflict; 354 svn_skel_t *markers; 355 356 SVN_ERR(conflict__get_conflict(&text_conflict, conflict_skel, 357 SVN_WC__CONFLICT_KIND_TEXT)); 358 359 SVN_ERR_ASSERT(!text_conflict); /* ### Use proper error? */ 360 361 /* Current skel format 362 ("text" 363 (OLD MINE OLD-THEIRS THEIRS)) */ 364 365 text_conflict = svn_skel__make_empty_list(result_pool); 366 markers = svn_skel__make_empty_list(result_pool); 367 368if (their_abspath) 369 { 370 const char *their_relpath; 371 372 SVN_ERR(svn_wc__db_to_relpath(&their_relpath, 373 db, wri_abspath, their_abspath, 374 result_pool, scratch_pool)); 375 svn_skel__prepend_str(their_relpath, markers, result_pool); 376 } 377 else 378 svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers); 379 380 if (mine_abspath) 381 { 382 const char *mine_relpath; 383 384 SVN_ERR(svn_wc__db_to_relpath(&mine_relpath, 385 db, wri_abspath, mine_abspath, 386 result_pool, scratch_pool)); 387 svn_skel__prepend_str(mine_relpath, markers, result_pool); 388 } 389 else 390 svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers); 391 392 if (their_old_abspath) 393 { 394 const char *original_relpath; 395 396 SVN_ERR(svn_wc__db_to_relpath(&original_relpath, 397 db, wri_abspath, their_old_abspath, 398 result_pool, scratch_pool)); 399 svn_skel__prepend_str(original_relpath, markers, result_pool); 400 } 401 else 402 svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers); 403 404 svn_skel__prepend(markers, text_conflict); 405 svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_TEXT, text_conflict, 406 result_pool); 407 408 /* And add it to the conflict skel */ 409 svn_skel__prepend(text_conflict, conflict_skel->children->next); 410 411 return SVN_NO_ERROR; 412} 413 414svn_error_t * 415svn_wc__conflict_skel_add_prop_conflict(svn_skel_t *conflict_skel, 416 svn_wc__db_t *db, 417 const char *wri_abspath, 418 const char *marker_abspath, 419 const apr_hash_t *mine_props, 420 const apr_hash_t *their_old_props, 421 const apr_hash_t *their_props, 422 const apr_hash_t *conflicted_prop_names, 423 apr_pool_t *result_pool, 424 apr_pool_t *scratch_pool) 425{ 426 svn_skel_t *prop_conflict; 427 svn_skel_t *props; 428 svn_skel_t *conflict_names; 429 svn_skel_t *markers; 430 apr_hash_index_t *hi; 431 432 SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel, 433 SVN_WC__CONFLICT_KIND_PROP)); 434 435 SVN_ERR_ASSERT(!prop_conflict); /* ### Use proper error? */ 436 437 /* This function currently implements: 438 ("prop" 439 ("marker_relpath") 440 prop-conflicted_prop_names 441 old-props 442 mine-props 443 their-props) 444 NULL lists are recorded as "" */ 445 /* ### Seems that this may not match what we read out. Read-out of 446 * 'theirs-old' comes as NULL. */ 447 448 prop_conflict = svn_skel__make_empty_list(result_pool); 449 450 if (their_props) 451 { 452 SVN_ERR(svn_skel__unparse_proplist(&props, their_props, result_pool)); 453 svn_skel__prepend(props, prop_conflict); 454 } 455 else 456 svn_skel__prepend_str("", prop_conflict, result_pool); /* No their_props */ 457 458 if (mine_props) 459 { 460 SVN_ERR(svn_skel__unparse_proplist(&props, mine_props, result_pool)); 461 svn_skel__prepend(props, prop_conflict); 462 } 463 else 464 svn_skel__prepend_str("", prop_conflict, result_pool); /* No mine_props */ 465 466 if (their_old_props) 467 { 468 SVN_ERR(svn_skel__unparse_proplist(&props, their_old_props, 469 result_pool)); 470 svn_skel__prepend(props, prop_conflict); 471 } 472 else 473 svn_skel__prepend_str("", prop_conflict, result_pool); /* No old_props */ 474 475 conflict_names = svn_skel__make_empty_list(result_pool); 476 for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)conflicted_prop_names); 477 hi; 478 hi = apr_hash_next(hi)) 479 { 480 svn_skel__prepend_str(apr_pstrdup(result_pool, apr_hash_this_key(hi)), 481 conflict_names, 482 result_pool); 483 } 484 svn_skel__prepend(conflict_names, prop_conflict); 485 486 markers = svn_skel__make_empty_list(result_pool); 487 488 if (marker_abspath) 489 { 490 const char *marker_relpath; 491 SVN_ERR(svn_wc__db_to_relpath(&marker_relpath, db, wri_abspath, 492 marker_abspath, 493 result_pool, scratch_pool)); 494 495 svn_skel__prepend_str(marker_relpath, markers, result_pool); 496 } 497/*else // ### set via svn_wc__conflict_create_markers 498 svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers);*/ 499 500 svn_skel__prepend(markers, prop_conflict); 501 502 svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_PROP, prop_conflict, result_pool); 503 504 /* And add it to the conflict skel */ 505 svn_skel__prepend(prop_conflict, conflict_skel->children->next); 506 507 return SVN_NO_ERROR; 508} 509 510/* A map for svn_wc_conflict_reason_t values. */ 511static const svn_token_map_t reason_map[] = 512{ 513 { "edited", svn_wc_conflict_reason_edited }, 514 { "obstructed", svn_wc_conflict_reason_obstructed }, 515 { "deleted", svn_wc_conflict_reason_deleted }, 516 { "missing", svn_wc_conflict_reason_missing }, 517 { "unversioned", svn_wc_conflict_reason_unversioned }, 518 { "added", svn_wc_conflict_reason_added }, 519 { "replaced", svn_wc_conflict_reason_replaced }, 520 { "moved-away", svn_wc_conflict_reason_moved_away }, 521 { "moved-here", svn_wc_conflict_reason_moved_here }, 522 { NULL } 523}; 524 525static const svn_token_map_t action_map[] = 526{ 527 { "edited", svn_wc_conflict_action_edit }, 528 { "added", svn_wc_conflict_action_add }, 529 { "deleted", svn_wc_conflict_action_delete }, 530 { "replaced", svn_wc_conflict_action_replace }, 531 { NULL } 532}; 533 534svn_error_t * 535svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel, 536 svn_wc__db_t *db, 537 const char *wri_abspath, 538 svn_wc_conflict_reason_t reason, 539 svn_wc_conflict_action_t action, 540 const char *move_src_op_root_abspath, 541 apr_pool_t *result_pool, 542 apr_pool_t *scratch_pool) 543{ 544 svn_skel_t *tree_conflict; 545 svn_skel_t *markers; 546 547 SVN_ERR(conflict__get_conflict(&tree_conflict, conflict_skel, 548 SVN_WC__CONFLICT_KIND_TREE)); 549 550 SVN_ERR_ASSERT(!tree_conflict); /* ### Use proper error? */ 551 552 SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_moved_away 553 || !move_src_op_root_abspath); /* ### Use proper error? */ 554 555 tree_conflict = svn_skel__make_empty_list(result_pool); 556 557 if (reason == svn_wc_conflict_reason_moved_away 558 && move_src_op_root_abspath) 559 { 560 const char *move_src_op_root_relpath; 561 562 SVN_ERR(svn_wc__db_to_relpath(&move_src_op_root_relpath, 563 db, wri_abspath, 564 move_src_op_root_abspath, 565 result_pool, scratch_pool)); 566 567 svn_skel__prepend_str(move_src_op_root_relpath, tree_conflict, 568 result_pool); 569 } 570 571 svn_skel__prepend_str(svn_token__to_word(action_map, action), 572 tree_conflict, result_pool); 573 574 svn_skel__prepend_str(svn_token__to_word(reason_map, reason), 575 tree_conflict, result_pool); 576 577 /* Tree conflicts have no marker files */ 578 markers = svn_skel__make_empty_list(result_pool); 579 svn_skel__prepend(markers, tree_conflict); 580 581 svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_TREE, tree_conflict, 582 result_pool); 583 584 /* And add it to the conflict skel */ 585 svn_skel__prepend(tree_conflict, conflict_skel->children->next); 586 587 return SVN_NO_ERROR; 588} 589 590svn_error_t * 591svn_wc__conflict_skel_resolve(svn_boolean_t *completely_resolved, 592 svn_skel_t *conflict_skel, 593 svn_wc__db_t *db, 594 const char *wri_abspath, 595 svn_boolean_t resolve_text, 596 const char *resolve_prop, 597 svn_boolean_t resolve_tree, 598 apr_pool_t *result_pool, 599 apr_pool_t *scratch_pool) 600{ 601 svn_skel_t *op; 602 svn_skel_t **pconflict; 603 SVN_ERR(conflict__get_operation(&op, conflict_skel)); 604 605 if (!op) 606 return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, 607 _("Not a completed conflict skel")); 608 609 /* We are going to drop items from a linked list. Instead of keeping 610 a pointer to the item we want to drop we store a pointer to the 611 pointer of what we may drop, to allow setting it to the next item. */ 612 613 pconflict = &(conflict_skel->children->next->children); 614 while (*pconflict) 615 { 616 svn_skel_t *c = (*pconflict)->children; 617 618 if (resolve_text 619 && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_TEXT)) 620 { 621 /* Remove the text conflict from the linked list */ 622 *pconflict = (*pconflict)->next; 623 continue; 624 } 625 else if (resolve_prop 626 && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_PROP)) 627 { 628 svn_skel_t **ppropnames = &(c->next->next->children); 629 630 if (resolve_prop[0] == '\0') 631 *ppropnames = NULL; /* remove all conflicted property names */ 632 else 633 while (*ppropnames) 634 { 635 if (svn_skel__matches_atom(*ppropnames, resolve_prop)) 636 { 637 *ppropnames = (*ppropnames)->next; 638 break; 639 } 640 ppropnames = &((*ppropnames)->next); 641 } 642 643 /* If no conflicted property names left */ 644 if (!c->next->next->children) 645 { 646 /* Remove the propery conflict skel from the linked list */ 647 *pconflict = (*pconflict)->next; 648 continue; 649 } 650 } 651 else if (resolve_tree 652 && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_TREE)) 653 { 654 /* Remove the tree conflict from the linked list */ 655 *pconflict = (*pconflict)->next; 656 continue; 657 } 658 659 pconflict = &((*pconflict)->next); 660 } 661 662 if (completely_resolved) 663 { 664 /* Nice, we can just call the complete function */ 665 svn_boolean_t complete_conflict; 666 SVN_ERR(svn_wc__conflict_skel_is_complete(&complete_conflict, 667 conflict_skel)); 668 669 *completely_resolved = !complete_conflict; 670 } 671 return SVN_NO_ERROR; 672} 673 674 675/* A map for svn_wc_operation_t values. */ 676static const svn_token_map_t operation_map[] = 677{ 678 { "", svn_wc_operation_none }, 679 { SVN_WC__CONFLICT_OP_UPDATE, svn_wc_operation_update }, 680 { SVN_WC__CONFLICT_OP_SWITCH, svn_wc_operation_switch }, 681 { SVN_WC__CONFLICT_OP_MERGE, svn_wc_operation_merge }, 682 { NULL } 683}; 684 685svn_error_t * 686svn_wc__conflict_read_info(svn_wc_operation_t *operation, 687 const apr_array_header_t **locations, 688 svn_boolean_t *text_conflicted, 689 svn_boolean_t *prop_conflicted, 690 svn_boolean_t *tree_conflicted, 691 svn_wc__db_t *db, 692 const char *wri_abspath, 693 const svn_skel_t *conflict_skel, 694 apr_pool_t *result_pool, 695 apr_pool_t *scratch_pool) 696{ 697 svn_skel_t *op; 698 const svn_skel_t *c; 699 700 SVN_ERR(conflict__get_operation(&op, conflict_skel)); 701 702 if (!op) 703 return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, 704 _("Not a completed conflict skel")); 705 706 c = op->children; 707 if (operation) 708 { 709 int value = svn_token__from_mem(operation_map, c->data, c->len); 710 711 if (value != SVN_TOKEN_UNKNOWN) 712 *operation = value; 713 else 714 *operation = svn_wc_operation_none; 715 } 716 c = c->next; 717 718 if (locations && c->children) 719 { 720 const svn_skel_t *loc_skel; 721 svn_wc_conflict_version_t *loc; 722 apr_array_header_t *locs = apr_array_make(result_pool, 2, sizeof(loc)); 723 724 for (loc_skel = c->children; loc_skel; loc_skel = loc_skel->next) 725 { 726 SVN_ERR(conflict__read_location(&loc, loc_skel, result_pool, 727 scratch_pool)); 728 729 APR_ARRAY_PUSH(locs, svn_wc_conflict_version_t *) = loc; 730 } 731 732 *locations = locs; 733 } 734 else if (locations) 735 *locations = NULL; 736 737 if (text_conflicted) 738 { 739 svn_skel_t *c_skel; 740 SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel, 741 SVN_WC__CONFLICT_KIND_TEXT)); 742 743 *text_conflicted = (c_skel != NULL); 744 } 745 746 if (prop_conflicted) 747 { 748 svn_skel_t *c_skel; 749 SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel, 750 SVN_WC__CONFLICT_KIND_PROP)); 751 752 *prop_conflicted = (c_skel != NULL); 753 } 754 755 if (tree_conflicted) 756 { 757 svn_skel_t *c_skel; 758 SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel, 759 SVN_WC__CONFLICT_KIND_TREE)); 760 761 *tree_conflicted = (c_skel != NULL); 762 } 763 764 return SVN_NO_ERROR; 765} 766 767 768svn_error_t * 769svn_wc__conflict_read_text_conflict(const char **mine_abspath, 770 const char **their_old_abspath, 771 const char **their_abspath, 772 svn_wc__db_t *db, 773 const char *wri_abspath, 774 const svn_skel_t *conflict_skel, 775 apr_pool_t *result_pool, 776 apr_pool_t *scratch_pool) 777{ 778 svn_skel_t *text_conflict; 779 const svn_skel_t *m; 780 781 SVN_ERR(conflict__get_conflict(&text_conflict, conflict_skel, 782 SVN_WC__CONFLICT_KIND_TEXT)); 783 784 if (!text_conflict) 785 return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set")); 786 787 m = text_conflict->children->next->children; 788 789 if (their_old_abspath) 790 { 791 if (m->is_atom) 792 { 793 const char *original_relpath; 794 795 original_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len); 796 SVN_ERR(svn_wc__db_from_relpath(their_old_abspath, 797 db, wri_abspath, original_relpath, 798 result_pool, scratch_pool)); 799 } 800 else 801 *their_old_abspath = NULL; 802 } 803 m = m->next; 804 805 if (mine_abspath) 806 { 807 if (m->is_atom) 808 { 809 const char *mine_relpath; 810 811 mine_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len); 812 SVN_ERR(svn_wc__db_from_relpath(mine_abspath, 813 db, wri_abspath, mine_relpath, 814 result_pool, scratch_pool)); 815 } 816 else 817 *mine_abspath = NULL; 818 } 819 m = m->next; 820 821 if (their_abspath) 822 { 823 if (m->is_atom) 824 { 825 const char *their_relpath; 826 827 their_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len); 828 SVN_ERR(svn_wc__db_from_relpath(their_abspath, 829 db, wri_abspath, their_relpath, 830 result_pool, scratch_pool)); 831 } 832 else 833 *their_abspath = NULL; 834 } 835 836 return SVN_NO_ERROR; 837} 838 839svn_error_t * 840svn_wc__conflict_read_prop_conflict(const char **marker_abspath, 841 apr_hash_t **mine_props, 842 apr_hash_t **their_old_props, 843 apr_hash_t **their_props, 844 apr_hash_t **conflicted_prop_names, 845 svn_wc__db_t *db, 846 const char *wri_abspath, 847 const svn_skel_t *conflict_skel, 848 apr_pool_t *result_pool, 849 apr_pool_t *scratch_pool) 850{ 851 svn_skel_t *prop_conflict; 852 const svn_skel_t *c; 853 854 SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel, 855 SVN_WC__CONFLICT_KIND_PROP)); 856 857 if (!prop_conflict) 858 return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set")); 859 860 c = prop_conflict->children; 861 862 c = c->next; /* Skip "prop" */ 863 864 /* Get marker file */ 865 if (marker_abspath) 866 { 867 const char *marker_relpath; 868 869 if (c->children && c->children->is_atom) 870 { 871 marker_relpath = apr_pstrmemdup(result_pool, c->children->data, 872 c->children->len); 873 874 SVN_ERR(svn_wc__db_from_relpath(marker_abspath, db, wri_abspath, 875 marker_relpath, 876 result_pool, scratch_pool)); 877 } 878 else 879 *marker_abspath = NULL; 880 } 881 c = c->next; 882 883 /* Get conflicted properties */ 884 if (conflicted_prop_names) 885 { 886 const svn_skel_t *name; 887 *conflicted_prop_names = apr_hash_make(result_pool); 888 889 for (name = c->children; name; name = name->next) 890 { 891 svn_hash_sets(*conflicted_prop_names, 892 apr_pstrmemdup(result_pool, name->data, name->len), 893 ""); 894 } 895 } 896 c = c->next; 897 898 /* Get original properties */ 899 if (their_old_props) 900 { 901 if (c->is_atom) 902 *their_old_props = apr_hash_make(result_pool); 903 else 904 SVN_ERR(svn_skel__parse_proplist(their_old_props, c, result_pool)); 905 } 906 c = c->next; 907 908 /* Get mine properties */ 909 if (mine_props) 910 { 911 if (c->is_atom) 912 *mine_props = apr_hash_make(result_pool); 913 else 914 SVN_ERR(svn_skel__parse_proplist(mine_props, c, result_pool)); 915 } 916 c = c->next; 917 918 /* Get their properties */ 919 if (their_props) 920 { 921 if (c->is_atom) 922 *their_props = apr_hash_make(result_pool); 923 else 924 SVN_ERR(svn_skel__parse_proplist(their_props, c, result_pool)); 925 } 926 927 return SVN_NO_ERROR; 928} 929 930svn_error_t * 931svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *reason, 932 svn_wc_conflict_action_t *action, 933 const char **move_src_op_root_abspath, 934 svn_wc__db_t *db, 935 const char *wri_abspath, 936 const svn_skel_t *conflict_skel, 937 apr_pool_t *result_pool, 938 apr_pool_t *scratch_pool) 939{ 940 svn_skel_t *tree_conflict; 941 const svn_skel_t *c; 942 svn_boolean_t is_moved_away = FALSE; 943 944 SVN_ERR(conflict__get_conflict(&tree_conflict, conflict_skel, 945 SVN_WC__CONFLICT_KIND_TREE)); 946 947 if (!tree_conflict) 948 return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set")); 949 950 c = tree_conflict->children; 951 952 c = c->next; /* Skip "tree" */ 953 954 c = c->next; /* Skip markers */ 955 956 { 957 int value = svn_token__from_mem(reason_map, c->data, c->len); 958 959 if (reason) 960 { 961 if (value != SVN_TOKEN_UNKNOWN) 962 *reason = value; 963 else 964 *reason = svn_wc_conflict_reason_edited; 965 } 966 967 is_moved_away = (value == svn_wc_conflict_reason_moved_away); 968 } 969 c = c->next; 970 971 if (action) 972 { 973 int value = svn_token__from_mem(action_map, c->data, c->len); 974 975 if (value != SVN_TOKEN_UNKNOWN) 976 *action = value; 977 else 978 *action = svn_wc_conflict_action_edit; 979 } 980 981 c = c->next; 982 983 if (move_src_op_root_abspath) 984 { 985 /* Only set for update and switch tree conflicts */ 986 if (c && is_moved_away) 987 { 988 const char *move_src_op_root_relpath 989 = apr_pstrmemdup(scratch_pool, c->data, c->len); 990 991 SVN_ERR(svn_wc__db_from_relpath(move_src_op_root_abspath, 992 db, wri_abspath, 993 move_src_op_root_relpath, 994 result_pool, scratch_pool)); 995 } 996 else 997 *move_src_op_root_abspath = NULL; 998 } 999 1000 return SVN_NO_ERROR; 1001} 1002 1003svn_error_t * 1004svn_wc__conflict_read_markers(const apr_array_header_t **markers, 1005 svn_wc__db_t *db, 1006 const char *wri_abspath, 1007 const svn_skel_t *conflict_skel, 1008 apr_pool_t *result_pool, 1009 apr_pool_t *scratch_pool) 1010{ 1011 const svn_skel_t *conflict; 1012 apr_array_header_t *list = NULL; 1013 1014 SVN_ERR_ASSERT(conflict_skel != NULL); 1015 1016 /* Walk the conflicts */ 1017 for (conflict = conflict_skel->children->next->children; 1018 conflict; 1019 conflict = conflict->next) 1020 { 1021 const svn_skel_t *marker; 1022 1023 /* Get the list of markers stored per conflict */ 1024 for (marker = conflict->children->next->children; 1025 marker; 1026 marker = marker->next) 1027 { 1028 /* Skip placeholders */ 1029 if (! marker->is_atom) 1030 continue; 1031 1032 if (! list) 1033 list = apr_array_make(result_pool, 4, sizeof(const char *)); 1034 1035 SVN_ERR(svn_wc__db_from_relpath( 1036 &APR_ARRAY_PUSH(list, const char*), 1037 db, wri_abspath, 1038 apr_pstrmemdup(scratch_pool, marker->data, 1039 marker->len), 1040 result_pool, scratch_pool)); 1041 } 1042 } 1043 *markers = list; 1044 1045 return SVN_NO_ERROR; 1046} 1047 1048/* -------------------------------------------------------------------- 1049 */ 1050 1051 1052svn_error_t * 1053svn_wc__conflict_create_markers(svn_skel_t **work_items, 1054 svn_wc__db_t *db, 1055 const char *local_abspath, 1056 svn_skel_t *conflict_skel, 1057 apr_pool_t *result_pool, 1058 apr_pool_t *scratch_pool) 1059{ 1060 svn_boolean_t prop_conflicted; 1061 svn_wc_operation_t operation; 1062 *work_items = NULL; 1063 1064 SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, 1065 NULL, &prop_conflicted, NULL, 1066 db, local_abspath, 1067 conflict_skel, 1068 scratch_pool, scratch_pool)); 1069 1070 if (prop_conflicted) 1071 { 1072 const char *marker_abspath = NULL; 1073 svn_node_kind_t kind; 1074 const char *marker_dir; 1075 const char *marker_name; 1076 const char *marker_relpath; 1077 1078 /* Ok, currently we have to do a few things for property conflicts: 1079 - Create a marker file 1080 - Store the name in the conflict_skel 1081 - Create a WQ item that fills the marker with the expected data */ 1082 1083 SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); 1084 1085 if (kind == svn_node_dir) 1086 { 1087 marker_dir = local_abspath; 1088 marker_name = SVN_WC__THIS_DIR_PREJ; 1089 } 1090 else 1091 svn_dirent_split(&marker_dir, &marker_name, local_abspath, 1092 scratch_pool); 1093 1094 SVN_ERR(svn_io_open_uniquely_named(NULL, &marker_abspath, 1095 marker_dir, 1096 marker_name, 1097 SVN_WC__PROP_REJ_EXT, 1098 svn_io_file_del_none, 1099 scratch_pool, scratch_pool)); 1100 1101 SVN_ERR(svn_wc__db_to_relpath(&marker_relpath, db, local_abspath, 1102 marker_abspath, result_pool, result_pool)); 1103 1104 /* And store the marker in the skel */ 1105 { 1106 svn_skel_t *prop_conflict; 1107 SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel, 1108 SVN_WC__CONFLICT_KIND_PROP)); 1109 1110 svn_skel__prepend_str(marker_relpath, prop_conflict->children->next, 1111 result_pool); 1112 } 1113 SVN_ERR(svn_wc__wq_build_prej_install(work_items, 1114 db, local_abspath, 1115 scratch_pool, scratch_pool)); 1116 } 1117 1118 return SVN_NO_ERROR; 1119} 1120 1121/* Helper function for the three apply_* functions below, used when 1122 * merging properties together. 1123 * 1124 * Given property PROPNAME on LOCAL_ABSPATH, and four possible property 1125 * values, generate four tmpfiles and pass them to CONFLICT_FUNC callback. 1126 * This gives the client an opportunity to interactively resolve the 1127 * property conflict. 1128 * 1129 * BASE_VAL/WORKING_VAL represent the current state of the working 1130 * copy, and INCOMING_OLD_VAL/INCOMING_NEW_VAL represents the incoming 1131 * propchange. Any of these values might be NULL, indicating either 1132 * non-existence or intent-to-delete. 1133 * 1134 * If the callback isn't available, or if it responds with 1135 * 'choose_postpone', then set *CONFLICT_REMAINS to TRUE and return. 1136 * 1137 * If the callback responds with a choice of 'base', 'theirs', 'mine', 1138 * or 'merged', then install the proper value into ACTUAL_PROPS and 1139 * set *CONFLICT_REMAINS to FALSE. 1140 */ 1141static svn_error_t * 1142generate_propconflict(svn_boolean_t *conflict_remains, 1143 svn_wc__db_t *db, 1144 const char *local_abspath, 1145 svn_node_kind_t kind, 1146 svn_wc_operation_t operation, 1147 const svn_wc_conflict_version_t *left_version, 1148 const svn_wc_conflict_version_t *right_version, 1149 const char *propname, 1150 const svn_string_t *base_val, 1151 const svn_string_t *working_val, 1152 const svn_string_t *incoming_old_val, 1153 const svn_string_t *incoming_new_val, 1154 svn_wc_conflict_resolver_func2_t conflict_func, 1155 void *conflict_baton, 1156 svn_cancel_func_t cancel_func, 1157 void *cancel_baton, 1158 apr_pool_t *scratch_pool) 1159{ 1160 svn_wc_conflict_result_t *result = NULL; 1161 svn_wc_conflict_description2_t *cdesc; 1162 const char *dirpath = svn_dirent_dirname(local_abspath, scratch_pool); 1163 const svn_string_t *new_value = NULL; 1164 1165 cdesc = svn_wc_conflict_description_create_prop2( 1166 local_abspath, 1167 kind, 1168 propname, scratch_pool); 1169 1170 cdesc->operation = operation; 1171 cdesc->src_left_version = left_version; 1172 cdesc->src_right_version = right_version; 1173 1174 /* Create a tmpfile for each of the string_t's we've got. */ 1175 if (working_val) 1176 { 1177 const char *file_name; 1178 1179 SVN_ERR(svn_io_write_unique(&file_name, dirpath, working_val->data, 1180 working_val->len, 1181 svn_io_file_del_on_pool_cleanup, 1182 scratch_pool)); 1183 cdesc->my_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); 1184 cdesc->prop_value_working = working_val; 1185 } 1186 1187 if (incoming_new_val) 1188 { 1189 const char *file_name; 1190 1191 SVN_ERR(svn_io_write_unique(&file_name, dirpath, incoming_new_val->data, 1192 incoming_new_val->len, 1193 svn_io_file_del_on_pool_cleanup, 1194 scratch_pool)); 1195 1196 /* ### For property conflicts, cd2 stores prop_reject_abspath in 1197 * ### their_abspath, and stores theirs_abspath in merged_file. */ 1198 cdesc->merged_file = svn_dirent_join(dirpath, file_name, scratch_pool); 1199 cdesc->prop_value_incoming_new = incoming_new_val; 1200 } 1201 1202 if (!base_val && !incoming_old_val) 1203 { 1204 /* If base and old are both NULL, then that's fine, we just let 1205 base_file stay NULL as-is. Both agents are attempting to add a 1206 new property. */ 1207 } 1208 else if ((base_val && !incoming_old_val) 1209 || (!base_val && incoming_old_val)) 1210 { 1211 /* If only one of base and old are defined, then we've got a 1212 situation where one agent is attempting to add the property 1213 for the first time, and the other agent is changing a 1214 property it thinks already exists. In this case, we return 1215 whichever older-value happens to be defined, so that the 1216 conflict-callback can still attempt a 3-way merge. */ 1217 1218 const svn_string_t *conflict_base_val = base_val ? base_val 1219 : incoming_old_val; 1220 const char *file_name; 1221 1222 SVN_ERR(svn_io_write_unique(&file_name, dirpath, 1223 conflict_base_val->data, 1224 conflict_base_val->len, 1225 svn_io_file_del_on_pool_cleanup, 1226 scratch_pool)); 1227 cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); 1228 } 1229 else /* base and old are both non-NULL */ 1230 { 1231 const svn_string_t *conflict_base_val; 1232 const char *file_name; 1233 1234 if (! svn_string_compare(base_val, incoming_old_val)) 1235 { 1236 /* What happens if 'base' and 'old' don't match up? In an 1237 ideal situation, they would. But if they don't, this is 1238 a classic example of a patch 'hunk' failing to apply due 1239 to a lack of context. For example: imagine that the user 1240 is busy changing the property from a value of "cat" to 1241 "dog", but the incoming propchange wants to change the 1242 same property value from "red" to "green". Total context 1243 mismatch. 1244 1245 HOWEVER: we can still pass one of the two base values as 1246 'base_file' to the callback anyway. It's still useful to 1247 present the working and new values to the user to 1248 compare. */ 1249 1250 if (working_val && svn_string_compare(base_val, working_val)) 1251 conflict_base_val = incoming_old_val; 1252 else 1253 conflict_base_val = base_val; 1254 } 1255 else 1256 { 1257 conflict_base_val = base_val; 1258 } 1259 1260 SVN_ERR(svn_io_write_unique(&file_name, dirpath, conflict_base_val->data, 1261 conflict_base_val->len, 1262 svn_io_file_del_on_pool_cleanup, scratch_pool)); 1263 cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); 1264 1265 cdesc->prop_value_base = base_val; 1266 cdesc->prop_value_incoming_old = incoming_old_val; 1267 1268 if (working_val && incoming_new_val) 1269 { 1270 svn_stream_t *mergestream; 1271 svn_diff_t *diff; 1272 svn_diff_file_options_t *options = 1273 svn_diff_file_options_create(scratch_pool); 1274 1275 SVN_ERR(svn_stream_open_unique(&mergestream, &cdesc->prop_reject_abspath, 1276 NULL, svn_io_file_del_on_pool_cleanup, 1277 scratch_pool, scratch_pool)); 1278 SVN_ERR(svn_diff_mem_string_diff3(&diff, conflict_base_val, 1279 working_val, 1280 incoming_new_val, options, scratch_pool)); 1281 SVN_ERR(svn_diff_mem_string_output_merge3(mergestream, diff, 1282 conflict_base_val, working_val, 1283 incoming_new_val, NULL, NULL, NULL, NULL, 1284 svn_diff_conflict_display_modified_latest, 1285 cancel_func, cancel_baton, scratch_pool)); 1286 SVN_ERR(svn_stream_close(mergestream)); 1287 1288 /* ### For property conflicts, cd2 stores prop_reject_abspath in 1289 * ### their_abspath, and stores theirs_abspath in merged_file. */ 1290 cdesc->their_abspath = cdesc->prop_reject_abspath; 1291 } 1292 } 1293 1294 if (!incoming_old_val && incoming_new_val) 1295 cdesc->action = svn_wc_conflict_action_add; 1296 else if (incoming_old_val && !incoming_new_val) 1297 cdesc->action = svn_wc_conflict_action_delete; 1298 else 1299 cdesc->action = svn_wc_conflict_action_edit; 1300 1301 if (base_val && !working_val) 1302 cdesc->reason = svn_wc_conflict_reason_deleted; 1303 else if (!base_val && working_val) 1304 cdesc->reason = svn_wc_conflict_reason_obstructed; 1305 else 1306 cdesc->reason = svn_wc_conflict_reason_edited; 1307 1308 /* Invoke the interactive conflict callback. */ 1309 SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, 1310 scratch_pool)); 1311 if (result == NULL) 1312 { 1313 *conflict_remains = TRUE; 1314 return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, 1315 NULL, _("Conflict callback violated API:" 1316 " returned no results")); 1317 } 1318 1319 1320 switch (result->choice) 1321 { 1322 default: 1323 case svn_wc_conflict_choose_postpone: 1324 { 1325 *conflict_remains = TRUE; 1326 break; 1327 } 1328 case svn_wc_conflict_choose_mine_full: 1329 { 1330 /* No need to change actual_props; it already contains working_val */ 1331 *conflict_remains = FALSE; 1332 new_value = working_val; 1333 break; 1334 } 1335 /* I think _mine_full and _theirs_full are appropriate for prop 1336 behavior as well as the text behavior. There should even be 1337 analogous behaviors for _mine and _theirs when those are 1338 ready, namely: fold in all non-conflicting prop changes, and 1339 then choose _mine side or _theirs side for conflicting ones. */ 1340 case svn_wc_conflict_choose_theirs_full: 1341 { 1342 *conflict_remains = FALSE; 1343 new_value = incoming_new_val; 1344 break; 1345 } 1346 case svn_wc_conflict_choose_base: 1347 { 1348 *conflict_remains = FALSE; 1349 new_value = base_val; 1350 break; 1351 } 1352 case svn_wc_conflict_choose_merged: 1353 { 1354 svn_stringbuf_t *merged_stringbuf; 1355 1356 if (!cdesc->merged_file 1357 && (!result->merged_file && !result->merged_value)) 1358 return svn_error_create 1359 (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, 1360 NULL, _("Conflict callback violated API:" 1361 " returned no merged file")); 1362 1363 if (result->merged_value) 1364 new_value = result->merged_value; 1365 else 1366 { 1367 SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf, 1368 result->merged_file ? 1369 result->merged_file : 1370 cdesc->merged_file, 1371 scratch_pool)); 1372 new_value = svn_stringbuf__morph_into_string(merged_stringbuf); 1373 } 1374 *conflict_remains = FALSE; 1375 break; 1376 } 1377 } 1378 1379 if (!*conflict_remains) 1380 { 1381 apr_hash_t *props; 1382 1383 /* For now, just set the property values. This should really do some of the 1384 more advanced things from svn_wc_prop_set() */ 1385 1386 SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool, 1387 scratch_pool)); 1388 1389 svn_hash_sets(props, propname, new_value); 1390 1391 SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, props, 1392 FALSE, NULL, NULL, 1393 scratch_pool)); 1394 } 1395 1396 return SVN_NO_ERROR; 1397} 1398 1399/* Perform a 3-way merge in which conflicts are expected, showing the 1400 * conflicts in the way specified by STYLE, and using MERGE_OPTIONS. 1401 * 1402 * The three input files are LEFT_ABSPATH (the base), DETRANSLATED_TARGET 1403 * and RIGHT_ABSPATH. The output is stored in a new temporary file, 1404 * whose name is put into *CHOSEN_ABSPATH. 1405 * 1406 * The output file will be deleted according to DELETE_WHEN. If 1407 * DELETE_WHEN is 'on pool cleanup', it refers to RESULT_POOL. 1408 * 1409 * DB and WRI_ABSPATH are used to choose a directory for the output file. 1410 * 1411 * Allocate *CHOSEN_ABSPATH in RESULT_POOL. Use SCRATCH_POOL for temporary 1412 * allocations. 1413 */ 1414static svn_error_t * 1415merge_showing_conflicts(const char **chosen_abspath, 1416 svn_wc__db_t *db, 1417 const char *wri_abspath, 1418 svn_diff_conflict_display_style_t style, 1419 const apr_array_header_t *merge_options, 1420 const char *left_abspath, 1421 const char *detranslated_target, 1422 const char *right_abspath, 1423 svn_io_file_del_t delete_when, 1424 svn_cancel_func_t cancel_func, 1425 void *cancel_baton, 1426 apr_pool_t *result_pool, 1427 apr_pool_t *scratch_pool) 1428{ 1429 const char *temp_dir; 1430 svn_stream_t *chosen_stream; 1431 svn_diff_t *diff; 1432 svn_diff_file_options_t *diff3_options; 1433 1434 diff3_options = svn_diff_file_options_create(scratch_pool); 1435 if (merge_options) 1436 SVN_ERR(svn_diff_file_options_parse(diff3_options, 1437 merge_options, 1438 scratch_pool)); 1439 1440 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, 1441 wri_abspath, 1442 scratch_pool, scratch_pool)); 1443 /* We need to open the stream in RESULT_POOL because that controls the 1444 * lifetime of the file if DELETE_WHEN is 'on pool cleanup'. (We also 1445 * want to allocate CHOSEN_ABSPATH in RESULT_POOL, but we don't care 1446 * about the stream itself.) */ 1447 SVN_ERR(svn_stream_open_unique(&chosen_stream, chosen_abspath, 1448 temp_dir, delete_when, 1449 result_pool, scratch_pool)); 1450 SVN_ERR(svn_diff_file_diff3_2(&diff, 1451 left_abspath, 1452 detranslated_target, right_abspath, 1453 diff3_options, scratch_pool)); 1454 SVN_ERR(svn_diff_file_output_merge3(chosen_stream, diff, 1455 left_abspath, 1456 detranslated_target, 1457 right_abspath, 1458 NULL, NULL, NULL, NULL, /* markers */ 1459 style, cancel_func, cancel_baton, 1460 scratch_pool)); 1461 SVN_ERR(svn_stream_close(chosen_stream)); 1462 1463 return SVN_NO_ERROR; 1464} 1465 1466/* Prepare to delete an artifact file at ARTIFACT_FILE_ABSPATH in the 1467 * working copy at DB/WRI_ABSPATH. 1468 * 1469 * Set *WORK_ITEMS to a new work item that, when run, will delete the 1470 * artifact file; or to NULL if there is no file to delete. 1471 * 1472 * Set *FILE_FOUND to TRUE if the artifact file is found on disk and its 1473 * node kind is 'file'; otherwise do not change *FILE_FOUND. FILE_FOUND 1474 * may be NULL if not required. 1475 */ 1476static svn_error_t * 1477remove_artifact_file_if_exists(svn_skel_t **work_items, 1478 svn_boolean_t *file_found, 1479 svn_wc__db_t *db, 1480 const char *wri_abspath, 1481 const char *artifact_file_abspath, 1482 apr_pool_t *result_pool, 1483 apr_pool_t *scratch_pool) 1484{ 1485 *work_items = NULL; 1486 if (artifact_file_abspath) 1487 { 1488 svn_node_kind_t node_kind; 1489 1490 SVN_ERR(svn_io_check_path(artifact_file_abspath, &node_kind, 1491 scratch_pool)); 1492 if (node_kind == svn_node_file) 1493 { 1494 SVN_ERR(svn_wc__wq_build_file_remove(work_items, 1495 db, wri_abspath, 1496 artifact_file_abspath, 1497 result_pool, scratch_pool)); 1498 if (file_found) 1499 *file_found = TRUE; 1500 } 1501 } 1502 1503 return SVN_NO_ERROR; 1504} 1505 1506/* Create a new file in the same directory as LOCAL_ABSPATH, with the 1507 same basename as LOCAL_ABSPATH, with a ".edited" extension, and set 1508 *WORK_ITEM to a new work item that will copy and translate from the file 1509 SOURCE_ABSPATH to that new file. It will be translated from repository- 1510 normal form to working-copy form according to the versioned properties 1511 of LOCAL_ABSPATH that are current when the work item is executed. 1512 1513 DB should have a write lock for the directory containing SOURCE. 1514 1515 Allocate *WORK_ITEM in RESULT_POOL. */ 1516static svn_error_t * 1517save_merge_result(svn_skel_t **work_item, 1518 svn_wc__db_t *db, 1519 const char *local_abspath, 1520 const char *source_abspath, 1521 apr_pool_t *result_pool, 1522 apr_pool_t *scratch_pool) 1523{ 1524 const char *edited_copy_abspath; 1525 const char *dir_abspath; 1526 const char *filename; 1527 1528 svn_dirent_split(&dir_abspath, &filename, local_abspath, scratch_pool); 1529 1530 /* ### Should use preserved-conflict-file-exts. */ 1531 /* Create the .edited file within this file's DIR_ABSPATH */ 1532 SVN_ERR(svn_io_open_uniquely_named(NULL, 1533 &edited_copy_abspath, 1534 dir_abspath, 1535 filename, 1536 ".edited", 1537 svn_io_file_del_none, 1538 scratch_pool, scratch_pool)); 1539 SVN_ERR(svn_wc__wq_build_file_copy_translated(work_item, 1540 db, local_abspath, 1541 source_abspath, 1542 edited_copy_abspath, 1543 result_pool, scratch_pool)); 1544 return SVN_NO_ERROR; 1545} 1546 1547 1548 1549/* Resolve the text conflict in CONFLICT, which is currently recorded 1550 * on DB/LOCAL_ABSPATH in the manner specified by CHOICE. 1551 * 1552 * Set *WORK_ITEMS to new work items that will make the on-disk changes 1553 * needed to complete the resolution (but not to mark it as resolved). 1554 * 1555 * Set *FOUND_ARTIFACT to true if conflict markers are removed; otherwise 1556 * (which is only if CHOICE is 'postpone') to false. 1557 * 1558 * CHOICE, MERGED_FILE and SAVE_MERGED are typically values provided by 1559 * the conflict resolver. 1560 * 1561 * MERGE_OPTIONS allows customizing the diff handling when using 1562 * per hunk conflict resolving. 1563 */ 1564static svn_error_t * 1565build_text_conflict_resolve_items(svn_skel_t **work_items, 1566 svn_boolean_t *found_artifact, 1567 svn_wc__db_t *db, 1568 const char *local_abspath, 1569 const svn_skel_t *conflict, 1570 svn_wc_conflict_choice_t choice, 1571 const char *merged_file, 1572 svn_boolean_t save_merged, 1573 const apr_array_header_t *merge_options, 1574 svn_cancel_func_t cancel_func, 1575 void *cancel_baton, 1576 apr_pool_t *result_pool, 1577 apr_pool_t *scratch_pool) 1578{ 1579 const char *mine_abspath; 1580 const char *their_old_abspath; 1581 const char *their_abspath; 1582 svn_skel_t *work_item; 1583 const char *install_from_abspath = NULL; 1584 svn_boolean_t remove_source = FALSE; 1585 1586 *work_items = NULL; 1587 1588 if (found_artifact) 1589 *found_artifact = FALSE; 1590 1591 SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath, 1592 &their_old_abspath, 1593 &their_abspath, 1594 db, local_abspath, 1595 conflict, 1596 scratch_pool, scratch_pool)); 1597 1598 if (save_merged) 1599 SVN_ERR(save_merge_result(work_items, 1600 db, local_abspath, 1601 merged_file 1602 ? merged_file 1603 : local_abspath, 1604 result_pool, scratch_pool)); 1605 1606 if (choice == svn_wc_conflict_choose_postpone) 1607 return SVN_NO_ERROR; 1608 1609 switch (choice) 1610 { 1611 /* If the callback wants to use one of the fulltexts 1612 to resolve the conflict, so be it.*/ 1613 case svn_wc_conflict_choose_base: 1614 { 1615 install_from_abspath = their_old_abspath; 1616 break; 1617 } 1618 case svn_wc_conflict_choose_theirs_full: 1619 { 1620 install_from_abspath = their_abspath; 1621 break; 1622 } 1623 case svn_wc_conflict_choose_mine_full: 1624 { 1625 /* In case of selecting to resolve the conflict choosing the full 1626 own file, allow the text conflict resolution to just take the 1627 existing local file if no merged file was present (case: binary 1628 file conflicts do not generate a locally merge file). 1629 */ 1630 install_from_abspath = mine_abspath 1631 ? mine_abspath 1632 : local_abspath; 1633 break; 1634 } 1635 case svn_wc_conflict_choose_theirs_conflict: 1636 case svn_wc_conflict_choose_mine_conflict: 1637 { 1638 svn_diff_conflict_display_style_t style 1639 = choice == svn_wc_conflict_choose_theirs_conflict 1640 ? svn_diff_conflict_display_latest 1641 : svn_diff_conflict_display_modified; 1642 1643 if (mine_abspath == NULL) 1644 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 1645 _("Conflict on '%s' cannot be resolved to " 1646 "'theirs-conflict' or 'mine-conflict' " 1647 "because a merged version of the file " 1648 "cannot be created."), 1649 svn_dirent_local_style(local_abspath, 1650 scratch_pool)); 1651 1652 SVN_ERR(merge_showing_conflicts(&install_from_abspath, 1653 db, local_abspath, 1654 style, merge_options, 1655 their_old_abspath, 1656 mine_abspath, 1657 their_abspath, 1658 /* ### why not same as other caller? */ 1659 svn_io_file_del_none, 1660 cancel_func, cancel_baton, 1661 scratch_pool, scratch_pool)); 1662 remove_source = TRUE; 1663 break; 1664 } 1665 1666 /* For the case of 3-way file merging, we don't 1667 really distinguish between these return values; 1668 if the callback claims to have "generally 1669 resolved" the situation, we still interpret 1670 that as "OK, we'll assume the merged version is 1671 good to use". */ 1672 case svn_wc_conflict_choose_merged: 1673 { 1674 install_from_abspath = merged_file 1675 ? merged_file 1676 : local_abspath; 1677 break; 1678 } 1679 case svn_wc_conflict_choose_postpone: 1680 { 1681 /* Assume conflict remains. */ 1682 return SVN_NO_ERROR; 1683 } 1684 default: 1685 SVN_ERR_ASSERT(choice == svn_wc_conflict_choose_postpone); 1686 } 1687 1688 if (install_from_abspath == NULL) 1689 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 1690 _("Conflict on '%s' could not be resolved " 1691 "because the chosen version of the file " 1692 "is not available."), 1693 svn_dirent_local_style(local_abspath, 1694 scratch_pool)); 1695 1696 /* ### It would be nice if we could somehow pass RECORD_FILEINFO 1697 as true in some easy cases. */ 1698 SVN_ERR(svn_wc__wq_build_file_install(&work_item, 1699 db, local_abspath, 1700 install_from_abspath, 1701 FALSE /* use_commit_times */, 1702 FALSE /* record_fileinfo */, 1703 result_pool, scratch_pool)); 1704 *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); 1705 1706 if (remove_source) 1707 { 1708 SVN_ERR(svn_wc__wq_build_file_remove(&work_item, 1709 db, local_abspath, 1710 install_from_abspath, 1711 result_pool, scratch_pool)); 1712 *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); 1713 } 1714 1715 SVN_ERR(remove_artifact_file_if_exists(&work_item, found_artifact, 1716 db, local_abspath, 1717 their_old_abspath, 1718 result_pool, scratch_pool)); 1719 *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); 1720 1721 SVN_ERR(remove_artifact_file_if_exists(&work_item, found_artifact, 1722 db, local_abspath, 1723 their_abspath, 1724 result_pool, scratch_pool)); 1725 *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); 1726 1727 SVN_ERR(remove_artifact_file_if_exists(&work_item, found_artifact, 1728 db, local_abspath, 1729 mine_abspath, 1730 result_pool, scratch_pool)); 1731 *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); 1732 1733 return SVN_NO_ERROR; 1734} 1735 1736 1737/* Set *DESC to a new description of the text conflict in 1738 * CONFLICT_SKEL. If there is no text conflict in CONFLICT_SKEL, return 1739 * an error. 1740 * 1741 * Use OPERATION and shallow copies of LEFT_VERSION and RIGHT_VERSION, 1742 * rather than reading them from CONFLICT_SKEL. Use IS_BINARY and 1743 * MIME_TYPE for the corresponding fields of *DESC. 1744 * 1745 * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary 1746 * allocations. */ 1747static svn_error_t * 1748read_text_conflict_desc(svn_wc_conflict_description2_t **desc, 1749 svn_wc__db_t *db, 1750 const char *local_abspath, 1751 const svn_skel_t *conflict_skel, 1752 const char *mime_type, 1753 svn_wc_operation_t operation, 1754 const svn_wc_conflict_version_t *left_version, 1755 const svn_wc_conflict_version_t *right_version, 1756 apr_pool_t *result_pool, 1757 apr_pool_t *scratch_pool) 1758{ 1759 *desc = svn_wc_conflict_description_create_text2(local_abspath, result_pool); 1760 (*desc)->mime_type = mime_type; 1761 (*desc)->is_binary = mime_type ? svn_mime_type_is_binary(mime_type) : FALSE; 1762 (*desc)->operation = operation; 1763 (*desc)->src_left_version = left_version; 1764 (*desc)->src_right_version = right_version; 1765 1766 SVN_ERR(svn_wc__conflict_read_text_conflict(&(*desc)->my_abspath, 1767 &(*desc)->base_abspath, 1768 &(*desc)->their_abspath, 1769 db, local_abspath, 1770 conflict_skel, 1771 result_pool, scratch_pool)); 1772 (*desc)->merged_file = apr_pstrdup(result_pool, local_abspath); 1773 1774 return SVN_NO_ERROR; 1775} 1776 1777/* Set *CONFLICT_DESC to a new description of the tree conflict in 1778 * CONFLICT_SKEL. If there is no tree conflict in CONFLICT_SKEL, return 1779 * an error. 1780 * 1781 * Use OPERATION and shallow copies of LEFT_VERSION and RIGHT_VERSION, 1782 * rather than reading them from CONFLICT_SKEL. 1783 * 1784 * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary 1785 * allocations. */ 1786static svn_error_t * 1787read_tree_conflict_desc(svn_wc_conflict_description2_t **desc, 1788 svn_wc__db_t *db, 1789 const char *local_abspath, 1790 svn_node_kind_t node_kind, 1791 const svn_skel_t *conflict_skel, 1792 svn_wc_operation_t operation, 1793 const svn_wc_conflict_version_t *left_version, 1794 const svn_wc_conflict_version_t *right_version, 1795 apr_pool_t *result_pool, 1796 apr_pool_t *scratch_pool) 1797{ 1798 svn_node_kind_t local_kind; 1799 svn_wc_conflict_reason_t reason; 1800 svn_wc_conflict_action_t action; 1801 1802 SVN_ERR(svn_wc__conflict_read_tree_conflict( 1803 &reason, &action, NULL, 1804 db, local_abspath, conflict_skel, scratch_pool, scratch_pool)); 1805 1806 if (reason == svn_wc_conflict_reason_missing) 1807 local_kind = svn_node_none; 1808 else if (reason == svn_wc_conflict_reason_unversioned || 1809 reason == svn_wc_conflict_reason_obstructed) 1810 SVN_ERR(svn_io_check_path(local_abspath, &local_kind, scratch_pool)); 1811 else if (action == svn_wc_conflict_action_delete 1812 && left_version 1813 && (operation == svn_wc_operation_update 1814 ||operation == svn_wc_operation_switch) 1815 && (reason == svn_wc_conflict_reason_deleted 1816 || reason == svn_wc_conflict_reason_moved_away)) 1817 { 1818 /* We have nothing locally to take the kind from */ 1819 local_kind = left_version->node_kind; 1820 } 1821 else 1822 local_kind = node_kind; 1823 1824 *desc = svn_wc_conflict_description_create_tree2(local_abspath, local_kind, 1825 operation, 1826 left_version, right_version, 1827 result_pool); 1828 (*desc)->reason = reason; 1829 (*desc)->action = action; 1830 1831 return SVN_NO_ERROR; 1832} 1833 1834/* Forward definition */ 1835static svn_error_t * 1836resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, 1837 svn_wc__db_t *db, 1838 const char *local_abspath, 1839 const svn_skel_t *conflict, 1840 svn_wc_conflict_choice_t conflict_choice, 1841 apr_hash_t *resolve_later, 1842 svn_wc_notify_func2_t notify_func, 1843 void *notify_baton, 1844 svn_cancel_func_t cancel_func, 1845 void *cancel_baton, 1846 apr_pool_t *scratch_pool); 1847 1848svn_error_t * 1849svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, 1850 const char *local_abspath, 1851 svn_node_kind_t kind, 1852 const svn_skel_t *conflict_skel, 1853 const apr_array_header_t *merge_options, 1854 svn_wc_conflict_resolver_func2_t resolver_func, 1855 void *resolver_baton, 1856 svn_cancel_func_t cancel_func, 1857 void *cancel_baton, 1858 apr_pool_t *scratch_pool) 1859{ 1860 svn_boolean_t text_conflicted; 1861 svn_boolean_t prop_conflicted; 1862 svn_boolean_t tree_conflicted; 1863 svn_wc_operation_t operation; 1864 const apr_array_header_t *locations; 1865 const svn_wc_conflict_version_t *left_version = NULL; 1866 const svn_wc_conflict_version_t *right_version = NULL; 1867 1868 SVN_ERR(svn_wc__conflict_read_info(&operation, &locations, 1869 &text_conflicted, &prop_conflicted, 1870 &tree_conflicted, 1871 db, local_abspath, conflict_skel, 1872 scratch_pool, scratch_pool)); 1873 1874 if (locations && locations->nelts > 0) 1875 left_version = APR_ARRAY_IDX(locations, 0, const svn_wc_conflict_version_t *); 1876 1877 if (locations && locations->nelts > 1) 1878 right_version = APR_ARRAY_IDX(locations, 1, const svn_wc_conflict_version_t *); 1879 1880 /* Quick and dirty compatibility wrapper. My guess would be that most resolvers 1881 would want to look at all properties at the same time. 1882 1883 ### svn currently only invokes this from the merge code to collect the list of 1884 ### conflicted paths. Eventually this code will be the base for 'svn resolve' 1885 ### and at that time the test coverage will improve 1886 */ 1887 if (prop_conflicted) 1888 { 1889 apr_hash_t *old_props; 1890 apr_hash_t *mine_props; 1891 apr_hash_t *their_props; 1892 apr_hash_t *old_their_props; 1893 apr_hash_t *conflicted; 1894 apr_pool_t *iterpool; 1895 apr_hash_index_t *hi; 1896 svn_boolean_t mark_resolved = TRUE; 1897 1898 SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL, 1899 &mine_props, 1900 &old_their_props, 1901 &their_props, 1902 &conflicted, 1903 db, local_abspath, 1904 conflict_skel, 1905 scratch_pool, scratch_pool)); 1906 1907 if (operation == svn_wc_operation_merge) 1908 SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, 1909 scratch_pool, scratch_pool)); 1910 else 1911 old_props = old_their_props; 1912 1913 iterpool = svn_pool_create(scratch_pool); 1914 1915 for (hi = apr_hash_first(scratch_pool, conflicted); 1916 hi; 1917 hi = apr_hash_next(hi)) 1918 { 1919 const char *propname = apr_hash_this_key(hi); 1920 svn_boolean_t conflict_remains = TRUE; 1921 1922 svn_pool_clear(iterpool); 1923 1924 if (cancel_func) 1925 SVN_ERR(cancel_func(cancel_baton)); 1926 1927 SVN_ERR(generate_propconflict(&conflict_remains, 1928 db, local_abspath, kind, 1929 operation, 1930 left_version, 1931 right_version, 1932 propname, 1933 old_props 1934 ? svn_hash_gets(old_props, propname) 1935 : NULL, 1936 mine_props 1937 ? svn_hash_gets(mine_props, propname) 1938 : NULL, 1939 old_their_props 1940 ? svn_hash_gets(old_their_props, propname) 1941 : NULL, 1942 their_props 1943 ? svn_hash_gets(their_props, propname) 1944 : NULL, 1945 resolver_func, resolver_baton, 1946 cancel_func, cancel_baton, 1947 iterpool)); 1948 1949 if (conflict_remains) 1950 mark_resolved = FALSE; 1951 } 1952 1953 if (mark_resolved) 1954 { 1955 SVN_ERR(svn_wc__mark_resolved_prop_conflicts(db, local_abspath, 1956 scratch_pool)); 1957 } 1958 svn_pool_destroy(iterpool); 1959 } 1960 1961 if (text_conflicted) 1962 { 1963 svn_skel_t *work_items; 1964 svn_boolean_t was_resolved; 1965 svn_wc_conflict_description2_t *desc; 1966 apr_hash_t *props; 1967 svn_wc_conflict_result_t *result; 1968 1969 SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, 1970 scratch_pool, scratch_pool)); 1971 1972 SVN_ERR(read_text_conflict_desc(&desc, 1973 db, local_abspath, conflict_skel, 1974 svn_prop_get_value(props, 1975 SVN_PROP_MIME_TYPE), 1976 operation, left_version, right_version, 1977 scratch_pool, scratch_pool)); 1978 1979 1980 work_items = NULL; 1981 was_resolved = FALSE; 1982 1983 /* Give the conflict resolution callback a chance to clean 1984 up the conflicts before we mark the file 'conflicted' */ 1985 1986 SVN_ERR(resolver_func(&result, desc, resolver_baton, scratch_pool, 1987 scratch_pool)); 1988 if (result == NULL) 1989 return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 1990 _("Conflict callback violated API:" 1991 " returned no results")); 1992 1993 SVN_ERR(build_text_conflict_resolve_items(&work_items, &was_resolved, 1994 db, local_abspath, 1995 conflict_skel, result->choice, 1996 result->merged_file, 1997 result->save_merged, 1998 merge_options, 1999 cancel_func, cancel_baton, 2000 scratch_pool, scratch_pool)); 2001 2002 if (result->choice != svn_wc_conflict_choose_postpone) 2003 { 2004 SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, 2005 TRUE, FALSE, FALSE, 2006 work_items, scratch_pool)); 2007 SVN_ERR(svn_wc__wq_run(db, local_abspath, 2008 cancel_func, cancel_baton, 2009 scratch_pool)); 2010 } 2011 } 2012 2013 if (tree_conflicted) 2014 { 2015 svn_wc_conflict_result_t *result; 2016 svn_wc_conflict_description2_t *desc; 2017 svn_boolean_t resolved; 2018 svn_node_kind_t node_kind; 2019 2020 SVN_ERR(svn_wc__db_read_kind(&node_kind, db, local_abspath, TRUE, 2021 TRUE, FALSE, scratch_pool)); 2022 2023 SVN_ERR(read_tree_conflict_desc(&desc, 2024 db, local_abspath, node_kind, 2025 conflict_skel, 2026 operation, left_version, right_version, 2027 scratch_pool, scratch_pool)); 2028 2029 /* Tell the resolver func about this conflict. */ 2030 SVN_ERR(resolver_func(&result, desc, resolver_baton, scratch_pool, 2031 scratch_pool)); 2032 2033 if (result == NULL) 2034 return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 2035 _("Conflict callback violated API:" 2036 " returned no results")); 2037 2038 /* Pass retry hash to avoid erroring out on cases where update 2039 can continue safely. ### Need notify handling */ 2040 if (result->choice != svn_wc_conflict_choose_postpone) 2041 SVN_ERR(resolve_tree_conflict_on_node(&resolved, 2042 db, local_abspath, conflict_skel, 2043 result->choice, 2044 apr_hash_make(scratch_pool), 2045 NULL, NULL, /* ### notify */ 2046 cancel_func, cancel_baton, 2047 scratch_pool)); 2048 } 2049 2050 return SVN_NO_ERROR; 2051} 2052 2053/* Read all property conflicts contained in CONFLICT_SKEL into 2054 * individual conflict descriptions, and append those descriptions 2055 * to the CONFLICTS array. If there is no property conflict in 2056 * CONFLICT_SKEL, return an error. 2057 * 2058 * If NOT create_tempfiles, always create a legacy property conflict 2059 * descriptor. 2060 * 2061 * Use NODE_KIND, OPERATION and shallow copies of LEFT_VERSION and 2062 * RIGHT_VERSION, rather than reading them from CONFLICT_SKEL. 2063 * 2064 * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary 2065 * allocations. */ 2066static svn_error_t * 2067read_prop_conflict_descs(apr_array_header_t *conflicts, 2068 svn_wc__db_t *db, 2069 const char *local_abspath, 2070 svn_skel_t *conflict_skel, 2071 svn_boolean_t create_tempfiles, 2072 svn_node_kind_t node_kind, 2073 svn_wc_operation_t operation, 2074 const svn_wc_conflict_version_t *left_version, 2075 const svn_wc_conflict_version_t *right_version, 2076 apr_pool_t *result_pool, 2077 apr_pool_t *scratch_pool) 2078{ 2079 const char *prop_reject_abspath; 2080 apr_hash_t *base_props; 2081 apr_hash_t *my_props; 2082 apr_hash_t *their_old_props; 2083 apr_hash_t *their_props; 2084 apr_hash_t *conflicted_props; 2085 apr_hash_index_t *hi; 2086 apr_pool_t *iterpool; 2087 svn_boolean_t prop_conflicted; 2088 2089 SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, &prop_conflicted, 2090 NULL, db, local_abspath, conflict_skel, 2091 scratch_pool, scratch_pool)); 2092 2093 if (!prop_conflicted) 2094 return SVN_NO_ERROR; 2095 2096 SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_abspath, 2097 &my_props, 2098 &their_old_props, 2099 &their_props, 2100 &conflicted_props, 2101 db, local_abspath, 2102 conflict_skel, 2103 scratch_pool, scratch_pool)); 2104 2105 prop_reject_abspath = apr_pstrdup(result_pool, prop_reject_abspath); 2106 2107 if (apr_hash_count(conflicted_props) == 0) 2108 { 2109 /* Legacy prop conflict with only a .reject file. */ 2110 svn_wc_conflict_description2_t *desc; 2111 2112 desc = svn_wc_conflict_description_create_prop2(local_abspath, 2113 node_kind, 2114 "", result_pool); 2115 2116 /* ### For property conflicts, cd2 stores prop_reject_abspath in 2117 * ### their_abspath, and stores theirs_abspath in merged_file. */ 2118 desc->prop_reject_abspath = prop_reject_abspath; /* in result_pool */ 2119 desc->their_abspath = desc->prop_reject_abspath; 2120 2121 desc->operation = operation; 2122 desc->src_left_version = left_version; 2123 desc->src_right_version = right_version; 2124 2125 APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t *) = desc; 2126 2127 return SVN_NO_ERROR; 2128 } 2129 2130 if (operation == svn_wc_operation_merge) 2131 SVN_ERR(svn_wc__db_read_pristine_props(&base_props, db, local_abspath, 2132 result_pool, scratch_pool)); 2133 else 2134 base_props = NULL; 2135 iterpool = svn_pool_create(scratch_pool); 2136 for (hi = apr_hash_first(scratch_pool, conflicted_props); 2137 hi; 2138 hi = apr_hash_next(hi)) 2139 { 2140 const char *propname = apr_hash_this_key(hi); 2141 svn_string_t *old_value; 2142 svn_string_t *my_value; 2143 svn_string_t *their_value; 2144 svn_wc_conflict_description2_t *desc; 2145 2146 svn_pool_clear(iterpool); 2147 2148 desc = svn_wc_conflict_description_create_prop2(local_abspath, 2149 node_kind, 2150 propname, 2151 result_pool); 2152 2153 desc->operation = operation; 2154 desc->src_left_version = left_version; 2155 desc->src_right_version = right_version; 2156 2157 desc->property_name = apr_pstrdup(result_pool, propname); 2158 2159 my_value = svn_hash_gets(my_props, propname); 2160 their_value = svn_hash_gets(their_props, propname); 2161 old_value = svn_hash_gets(their_old_props, propname); 2162 2163 /* Compute the incoming side of the conflict ('action'). */ 2164 if (their_value == NULL) 2165 desc->action = svn_wc_conflict_action_delete; 2166 else if (old_value == NULL) 2167 desc->action = svn_wc_conflict_action_add; 2168 else 2169 desc->action = svn_wc_conflict_action_edit; 2170 2171 /* Compute the local side of the conflict ('reason'). */ 2172 if (my_value == NULL) 2173 desc->reason = svn_wc_conflict_reason_deleted; 2174 else if (old_value == NULL) 2175 desc->reason = svn_wc_conflict_reason_added; 2176 else 2177 desc->reason = svn_wc_conflict_reason_edited; 2178 2179 /* ### For property conflicts, cd2 stores prop_reject_abspath in 2180 * ### their_abspath, and stores theirs_abspath in merged_file. */ 2181 desc->prop_reject_abspath = prop_reject_abspath; /* in result_pool */ 2182 desc->their_abspath = desc->prop_reject_abspath; 2183 2184 desc->prop_value_base = base_props ? svn_hash_gets(base_props, propname) 2185 : desc->prop_value_incoming_old; 2186 2187 if (my_value) 2188 { 2189 svn_stream_t *s; 2190 apr_size_t len; 2191 2192 if (create_tempfiles) 2193 { 2194 SVN_ERR(svn_stream_open_unique(&s, &desc->my_abspath, NULL, 2195 svn_io_file_del_on_pool_cleanup, 2196 result_pool, iterpool)); 2197 len = my_value->len; 2198 SVN_ERR(svn_stream_write(s, my_value->data, &len)); 2199 SVN_ERR(svn_stream_close(s)); 2200 } 2201 2202 desc->prop_value_working = svn_string_dup(my_value, result_pool); 2203 } 2204 2205 if (their_value) 2206 { 2207 svn_stream_t *s; 2208 apr_size_t len; 2209 2210 /* ### For property conflicts, cd2 stores prop_reject_abspath in 2211 * ### their_abspath, and stores theirs_abspath in merged_file. */ 2212 if (create_tempfiles) 2213 { 2214 SVN_ERR(svn_stream_open_unique(&s, &desc->merged_file, NULL, 2215 svn_io_file_del_on_pool_cleanup, 2216 result_pool, iterpool)); 2217 len = their_value->len; 2218 SVN_ERR(svn_stream_write(s, their_value->data, &len)); 2219 SVN_ERR(svn_stream_close(s)); 2220 } 2221 2222 desc->prop_value_incoming_new = svn_string_dup(their_value, result_pool); 2223 } 2224 2225 if (old_value) 2226 { 2227 svn_stream_t *s; 2228 apr_size_t len; 2229 2230 if (create_tempfiles) 2231 { 2232 SVN_ERR(svn_stream_open_unique(&s, &desc->base_abspath, NULL, 2233 svn_io_file_del_on_pool_cleanup, 2234 result_pool, iterpool)); 2235 len = old_value->len; 2236 SVN_ERR(svn_stream_write(s, old_value->data, &len)); 2237 SVN_ERR(svn_stream_close(s)); 2238 } 2239 2240 desc->prop_value_incoming_old = svn_string_dup(old_value, result_pool); 2241 } 2242 2243 APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t *) = desc; 2244 } 2245 svn_pool_destroy(iterpool); 2246 2247 return SVN_NO_ERROR; 2248} 2249 2250svn_error_t * 2251svn_wc__read_conflicts(const apr_array_header_t **conflicts, 2252 svn_skel_t **conflict_skel, 2253 svn_wc__db_t *db, 2254 const char *local_abspath, 2255 svn_boolean_t create_tempfiles, 2256 svn_boolean_t only_tree_conflict, 2257 apr_pool_t *result_pool, 2258 apr_pool_t *scratch_pool) 2259{ 2260 svn_skel_t *the_conflict_skel; 2261 apr_array_header_t *cflcts; 2262 svn_boolean_t prop_conflicted; 2263 svn_boolean_t text_conflicted; 2264 svn_boolean_t tree_conflicted; 2265 svn_wc_operation_t operation; 2266 const apr_array_header_t *locations; 2267 const svn_wc_conflict_version_t *left_version = NULL; 2268 const svn_wc_conflict_version_t *right_version = NULL; 2269 svn_node_kind_t node_kind; 2270 apr_hash_t *props; 2271 2272 if (!conflict_skel) 2273 conflict_skel = &the_conflict_skel; 2274 2275 SVN_ERR(svn_wc__db_read_conflict(conflict_skel, &node_kind, &props, 2276 db, local_abspath, 2277 (conflict_skel == &the_conflict_skel) 2278 ? scratch_pool 2279 : result_pool, 2280 scratch_pool)); 2281 2282 if (!*conflict_skel) 2283 { 2284 /* Some callers expect not NULL */ 2285 *conflicts = apr_array_make(result_pool, 0, 2286 sizeof(svn_wc_conflict_description2_t *)); 2287 return SVN_NO_ERROR; 2288 } 2289 2290 SVN_ERR(svn_wc__conflict_read_info(&operation, &locations, &text_conflicted, 2291 &prop_conflicted, &tree_conflicted, 2292 db, local_abspath, *conflict_skel, 2293 result_pool, scratch_pool)); 2294 2295 cflcts = apr_array_make(result_pool, 4, 2296 sizeof(svn_wc_conflict_description2_t *)); 2297 2298 if (locations && locations->nelts > 0) 2299 left_version = APR_ARRAY_IDX(locations, 0, const svn_wc_conflict_version_t *); 2300 if (locations && locations->nelts > 1) 2301 right_version = APR_ARRAY_IDX(locations, 1, const svn_wc_conflict_version_t *); 2302 2303 if (prop_conflicted && !only_tree_conflict) 2304 { 2305 SVN_ERR(read_prop_conflict_descs(cflcts, 2306 db, local_abspath, *conflict_skel, 2307 create_tempfiles, node_kind, 2308 operation, left_version, right_version, 2309 result_pool, scratch_pool)); 2310 } 2311 2312 if (text_conflicted && !only_tree_conflict) 2313 { 2314 svn_wc_conflict_description2_t *desc; 2315 2316 SVN_ERR(read_text_conflict_desc(&desc, 2317 db, local_abspath, *conflict_skel, 2318 svn_prop_get_value(props, 2319 SVN_PROP_MIME_TYPE), 2320 operation, left_version, right_version, 2321 result_pool, scratch_pool)); 2322 APR_ARRAY_PUSH(cflcts, svn_wc_conflict_description2_t *) = desc; 2323 } 2324 2325 if (tree_conflicted) 2326 { 2327 svn_wc_conflict_description2_t *desc; 2328 2329 SVN_ERR(read_tree_conflict_desc(&desc, 2330 db, local_abspath, node_kind, 2331 *conflict_skel, 2332 operation, left_version, right_version, 2333 result_pool, scratch_pool)); 2334 2335 APR_ARRAY_PUSH(cflcts, const svn_wc_conflict_description2_t *) = desc; 2336 } 2337 2338 *conflicts = cflcts; 2339 return SVN_NO_ERROR; 2340} 2341 2342 2343/*** Resolving a conflict automatically ***/ 2344 2345/* 2346 * Resolve the property conflicts found in DB/LOCAL_ABSPATH according 2347 * to CONFLICT_CHOICE. 2348 * 2349 * It is not an error if there is no prop conflict. If a prop conflict 2350 * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. 2351 * 2352 * Note: When there are no conflict markers on-disk to remove there is 2353 * no existing text conflict (unless we are still in the process of 2354 * creating the text conflict and we didn't register a marker file yet). 2355 * In this case the database contains old information, which we should 2356 * remove to avoid checking the next time. Resolving a property conflict 2357 * by just removing the marker file is a fully supported scenario since 2358 * Subversion 1.0. 2359 * 2360 * ### TODO [JAF] The '*_full' and '*_conflict' choices should differ. 2361 * In my opinion, 'mine_full'/'theirs_full' should select 2362 * the entire set of properties from 'mine' or 'theirs' respectively, 2363 * while 'mine_conflict'/'theirs_conflict' should select just the 2364 * properties that are in conflict. Or, '_full' should select the 2365 * entire property whereas '_conflict' should do a text merge within 2366 * each property, selecting hunks. Or all three kinds of behaviour 2367 * should be available (full set of props, full value of conflicting 2368 * props, or conflicting text hunks). 2369 * ### BH: If we make *_full select the full set of properties, we should 2370 * check if we shouldn't make it also select the full text for files. 2371 * 2372 * ### TODO [JAF] All this complexity should not be down here in libsvn_wc 2373 * but in a layer above. 2374 * 2375 * ### TODO [JAF] Options for 'base' should be like options for 'mine' and 2376 * for 'theirs' -- choose full set of props, full value of conflicting 2377 * props, or conflicting text hunks. 2378 * 2379 */ 2380static svn_error_t * 2381resolve_prop_conflict_on_node(svn_boolean_t *did_resolve, 2382 svn_wc__db_t *db, 2383 const char *local_abspath, 2384 svn_skel_t *conflicts, 2385 const char *conflicted_propname, 2386 svn_wc_conflict_choice_t conflict_choice, 2387 const char *merged_file, 2388 const svn_string_t *merged_value, 2389 svn_cancel_func_t cancel_func, 2390 void *cancel_baton, 2391 apr_pool_t *scratch_pool) 2392{ 2393 const char *prop_reject_file; 2394 apr_hash_t *mine_props; 2395 apr_hash_t *their_old_props; 2396 apr_hash_t *their_props; 2397 apr_hash_t *conflicted_props; 2398 apr_hash_t *old_props; 2399 apr_hash_t *resolve_from = NULL; 2400 svn_skel_t *work_items = NULL; 2401 svn_wc_operation_t operation; 2402 svn_boolean_t prop_conflicted; 2403 apr_hash_t *actual_props; 2404 svn_boolean_t resolved_all, resolved_all_prop; 2405 2406 *did_resolve = FALSE; 2407 2408 SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, &prop_conflicted, 2409 NULL, db, local_abspath, conflicts, 2410 scratch_pool, scratch_pool)); 2411 if (!prop_conflicted) 2412 return SVN_NO_ERROR; 2413 2414 SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file, 2415 &mine_props, &their_old_props, 2416 &their_props, &conflicted_props, 2417 db, local_abspath, conflicts, 2418 scratch_pool, scratch_pool)); 2419 2420 if (!conflicted_props) 2421 { 2422 /* We have a pre 1.8 property conflict. Just mark it resolved */ 2423 2424 SVN_ERR(remove_artifact_file_if_exists(&work_items, did_resolve, 2425 db, local_abspath, prop_reject_file, 2426 scratch_pool, scratch_pool)); 2427 SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, TRUE, FALSE, 2428 work_items, scratch_pool)); 2429 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, 2430 scratch_pool)); 2431 return SVN_NO_ERROR; 2432 } 2433 2434 if (conflicted_propname[0] != '\0' 2435 && !svn_hash_gets(conflicted_props, conflicted_propname)) 2436 { 2437 return SVN_NO_ERROR; /* This property is not conflicted! */ 2438 } 2439 2440 if (operation == svn_wc_operation_merge) 2441 SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, 2442 scratch_pool, scratch_pool)); 2443 else 2444 old_props = their_old_props; 2445 2446 SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, 2447 scratch_pool, scratch_pool)); 2448 2449 /* We currently handle *_conflict as *_full as this argument is currently 2450 always applied for all conflicts on a node at the same time. Giving 2451 an error would break some tests that assumed that this would just 2452 resolve property conflicts to working. 2453 2454 An alternative way to handle these conflicts would be to just copy all 2455 property state from mine/theirs on the _full option instead of just the 2456 conflicted properties. In some ways this feels like a sensible option as 2457 that would take both properties and text from mine/theirs, but when not 2458 both properties and text are conflicted we would fail in doing so. 2459 */ 2460 switch (conflict_choice) 2461 { 2462 case svn_wc_conflict_choose_base: 2463 resolve_from = their_old_props ? their_old_props : old_props; 2464 break; 2465 case svn_wc_conflict_choose_mine_full: 2466 case svn_wc_conflict_choose_mine_conflict: 2467 resolve_from = mine_props; 2468 break; 2469 case svn_wc_conflict_choose_theirs_full: 2470 case svn_wc_conflict_choose_theirs_conflict: 2471 resolve_from = their_props; 2472 break; 2473 case svn_wc_conflict_choose_merged: 2474 if ((merged_file || merged_value) && conflicted_propname[0] != '\0') 2475 { 2476 resolve_from = apr_hash_copy(scratch_pool, actual_props); 2477 2478 if (!merged_value) 2479 { 2480 svn_stream_t *stream; 2481 svn_string_t *merged_propval; 2482 2483 SVN_ERR(svn_stream_open_readonly(&stream, merged_file, 2484 scratch_pool, scratch_pool)); 2485 SVN_ERR(svn_string_from_stream(&merged_propval, stream, 2486 scratch_pool, scratch_pool)); 2487 2488 merged_value = merged_propval; 2489 } 2490 svn_hash_sets(resolve_from, conflicted_propname, merged_value); 2491 } 2492 else 2493 resolve_from = NULL; 2494 break; 2495 default: 2496 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 2497 _("Invalid 'conflict_result' argument")); 2498 } 2499 2500 2501 if (resolve_from) 2502 { 2503 apr_hash_index_t *hi; 2504 apr_hash_t *apply_on_props; 2505 2506 if (conflicted_propname[0] == '\0') 2507 { 2508 /* Apply to all conflicted properties */ 2509 apply_on_props = conflicted_props; 2510 } 2511 else 2512 { 2513 /* Apply to a single property */ 2514 apply_on_props = apr_hash_make(scratch_pool); 2515 svn_hash_sets(apply_on_props, conflicted_propname, ""); 2516 } 2517 2518 /* Apply the selected changes */ 2519 for (hi = apr_hash_first(scratch_pool, apply_on_props); 2520 hi; 2521 hi = apr_hash_next(hi)) 2522 { 2523 const char *propname = apr_hash_this_key(hi); 2524 svn_string_t *new_value = NULL; 2525 2526 new_value = svn_hash_gets(resolve_from, propname); 2527 2528 svn_hash_sets(actual_props, propname, new_value); 2529 } 2530 } 2531 /*else the user accepted the properties as-is */ 2532 2533 /* This function handles conflicted_propname "" as resolving 2534 all property conflicts... Just what we need here */ 2535 SVN_ERR(svn_wc__conflict_skel_resolve(&resolved_all, conflicts, 2536 db, local_abspath, 2537 FALSE, conflicted_propname, 2538 FALSE, 2539 scratch_pool, scratch_pool)); 2540 2541 if (!resolved_all) 2542 { 2543 /* Are there still property conflicts left? (or only...) */ 2544 SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, NULL, &prop_conflicted, 2545 NULL, db, local_abspath, conflicts, 2546 scratch_pool, scratch_pool)); 2547 2548 resolved_all_prop = (! prop_conflicted); 2549 } 2550 else 2551 { 2552 resolved_all_prop = TRUE; 2553 conflicts = NULL; 2554 } 2555 2556 if (resolved_all_prop) 2557 { 2558 /* Legacy behavior: Only report property conflicts as resolved when the 2559 property reject file exists 2560 2561 If not the UI shows the conflict as already resolved 2562 (and in this case we just remove the in-db conflict) */ 2563 SVN_ERR(remove_artifact_file_if_exists(&work_items, did_resolve, 2564 db, local_abspath, 2565 prop_reject_file, 2566 scratch_pool, scratch_pool)); 2567 } 2568 else 2569 { 2570 /* Create a new prej file, based on the remaining conflicts */ 2571 SVN_ERR(svn_wc__wq_build_prej_install(&work_items, 2572 db, local_abspath, 2573 scratch_pool, scratch_pool)); 2574 *did_resolve = TRUE; /* We resolved a property conflict */ 2575 } 2576 2577 /* This installs the updated conflict skel */ 2578 SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, actual_props, 2579 FALSE, conflicts, work_items, 2580 scratch_pool)); 2581 2582 if (resolved_all) 2583 { 2584 /* Remove the whole conflict. Should probably be integrated 2585 into the op_set_props() call */ 2586 SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, 2587 FALSE, TRUE, FALSE, 2588 NULL, scratch_pool)); 2589 } 2590 2591 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, 2592 scratch_pool)); 2593 2594 return SVN_NO_ERROR; 2595} 2596 2597/* 2598 * Resolve the tree conflict found in DB/LOCAL_ABSPATH according to 2599 * CONFLICT_CHOICE. 2600 * 2601 * It is not an error if there is no tree conflict. If a tree conflict 2602 * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. 2603 * 2604 * It is not an error if there is no tree conflict. 2605 * 2606 * If the conflict can't be resolved yet because another tree conflict is 2607 * blocking a storage location, store the tree conflict in the RESOLVE_LATER 2608 * hash. 2609 */ 2610static svn_error_t * 2611resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, 2612 svn_wc__db_t *db, 2613 const char *local_abspath, 2614 const svn_skel_t *conflicts, 2615 svn_wc_conflict_choice_t conflict_choice, 2616 apr_hash_t *resolve_later, 2617 svn_wc_notify_func2_t notify_func, 2618 void *notify_baton, 2619 svn_cancel_func_t cancel_func, 2620 void *cancel_baton, 2621 apr_pool_t *scratch_pool) 2622{ 2623 svn_wc_conflict_reason_t reason; 2624 svn_wc_conflict_action_t action; 2625 svn_wc_operation_t operation; 2626 svn_boolean_t tree_conflicted; 2627 const char *src_op_root_abspath; 2628 2629 *did_resolve = FALSE; 2630 2631 SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, 2632 &tree_conflicted, db, local_abspath, 2633 conflicts, scratch_pool, scratch_pool)); 2634 if (!tree_conflicted) 2635 return SVN_NO_ERROR; 2636 2637 SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, 2638 &src_op_root_abspath, 2639 db, local_abspath, 2640 conflicts, 2641 scratch_pool, scratch_pool)); 2642 2643 if (operation == svn_wc_operation_update 2644 || operation == svn_wc_operation_switch) 2645 { 2646 svn_error_t *err; 2647 if (reason == svn_wc_conflict_reason_deleted || 2648 reason == svn_wc_conflict_reason_replaced) 2649 { 2650 if (conflict_choice == svn_wc_conflict_choose_merged) 2651 { 2652 /* Break moves for any children moved out of this directory, 2653 * and leave this directory deleted. */ 2654 2655 if (action != svn_wc_conflict_action_delete) 2656 { 2657 SVN_ERR(svn_wc__db_op_break_moved_away( 2658 db, local_abspath, src_op_root_abspath, TRUE, 2659 notify_func, notify_baton, 2660 scratch_pool)); 2661 *did_resolve = TRUE; 2662 return SVN_NO_ERROR; /* Marked resolved by function*/ 2663 } 2664 /* else # The move is/moves are already broken */ 2665 2666 2667 *did_resolve = TRUE; 2668 } 2669 else if (conflict_choice == svn_wc_conflict_choose_mine_conflict) 2670 { 2671 svn_skel_t *new_conflicts; 2672 2673 /* Raise moved-away conflicts on any children moved out of 2674 * this directory, and leave this directory as-is. 2675 * 2676 * The newly conflicted moved-away children will be updated 2677 * if they are resolved with 'mine_conflict' as well. */ 2678 err = svn_wc__db_op_raise_moved_away( 2679 db, local_abspath, notify_func, notify_baton, 2680 scratch_pool); 2681 2682 if (err) 2683 { 2684 const char *dup_abspath; 2685 2686 if (!resolve_later 2687 || err->apr_err != SVN_ERR_WC_OBSTRUCTED_UPDATE) 2688 return svn_error_trace(err); 2689 2690 svn_error_clear(err); 2691 dup_abspath = apr_pstrdup(apr_hash_pool_get(resolve_later), 2692 local_abspath); 2693 2694 svn_hash_sets(resolve_later, dup_abspath, dup_abspath); 2695 2696 return SVN_NO_ERROR; /* Retry after other conflicts */ 2697 } 2698 2699 /* We might now have a moved-away on *this* path, let's 2700 try to resolve that directly if that is the case */ 2701 SVN_ERR(svn_wc__db_read_conflict(&new_conflicts, NULL, NULL, 2702 db, local_abspath, 2703 scratch_pool, scratch_pool)); 2704 2705 if (new_conflicts) 2706 SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, NULL, NULL, 2707 &tree_conflicted, 2708 db, local_abspath, 2709 new_conflicts, 2710 scratch_pool, 2711 scratch_pool)); 2712 2713 if (!new_conflicts || !tree_conflicted) 2714 { 2715 /* TC is marked resolved by calling 2716 svn_wc__db_resolve_delete_raise_moved_away */ 2717 *did_resolve = TRUE; 2718 return SVN_NO_ERROR; 2719 } 2720 2721 SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, 2722 &src_op_root_abspath, 2723 db, local_abspath, 2724 new_conflicts, 2725 scratch_pool, 2726 scratch_pool)); 2727 2728 if (reason != svn_wc_conflict_reason_moved_away) 2729 { 2730 *did_resolve = TRUE; 2731 return SVN_NO_ERROR; /* We fixed one, but... */ 2732 } 2733 2734 conflicts = new_conflicts; 2735 /* Fall through in moved_away handling */ 2736 } 2737 else 2738 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, 2739 NULL, 2740 _("Tree conflict can only be resolved to " 2741 "'working' or 'mine-conflict' state; " 2742 "'%s' not resolved"), 2743 svn_dirent_local_style(local_abspath, 2744 scratch_pool)); 2745 } 2746 2747 if (reason == svn_wc_conflict_reason_moved_away 2748 && action == svn_wc_conflict_action_edit) 2749 { 2750 /* After updates, we can resolve local moved-away 2751 * vs. any incoming change, either by updating the 2752 * moved-away node (mine-conflict) or by breaking the 2753 * move (theirs-conflict). */ 2754 if (conflict_choice == svn_wc_conflict_choose_mine_conflict) 2755 { 2756 err = svn_wc__db_update_moved_away_conflict_victim( 2757 db, local_abspath, src_op_root_abspath, 2758 operation, action, reason, 2759 cancel_func, cancel_baton, 2760 notify_func, notify_baton, 2761 scratch_pool); 2762 2763 if (err) 2764 { 2765 const char *dup_abspath; 2766 2767 if (!resolve_later 2768 || err->apr_err != SVN_ERR_WC_OBSTRUCTED_UPDATE) 2769 return svn_error_trace(err); 2770 2771 svn_error_clear(err); 2772 dup_abspath = apr_pstrdup(apr_hash_pool_get(resolve_later), 2773 local_abspath); 2774 2775 svn_hash_sets(resolve_later, dup_abspath, dup_abspath); 2776 2777 return SVN_NO_ERROR; /* Retry after other conflicts */ 2778 } 2779 else 2780 *did_resolve = TRUE; 2781 } 2782 else if (conflict_choice == svn_wc_conflict_choose_merged) 2783 { 2784 /* We must break the move if the user accepts the current 2785 * working copy state instead of updating the move. 2786 * Else the move would be left in an invalid state. */ 2787 2788 SVN_ERR(svn_wc__db_op_break_moved_away(db, local_abspath, 2789 src_op_root_abspath, TRUE, 2790 notify_func, notify_baton, 2791 scratch_pool)); 2792 *did_resolve = TRUE; 2793 return SVN_NO_ERROR; /* Conflict is marked resolved */ 2794 } 2795 else 2796 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, 2797 NULL, 2798 _("Tree conflict can only be resolved to " 2799 "'working' or 'mine-conflict' state; " 2800 "'%s' not resolved"), 2801 svn_dirent_local_style(local_abspath, 2802 scratch_pool)); 2803 } 2804 else if (reason == svn_wc_conflict_reason_moved_away 2805 && action != svn_wc_conflict_action_edit) 2806 { 2807 /* action added is impossible, because that would imply that 2808 something was added, but before that already moved... 2809 (which would imply a replace) */ 2810 SVN_ERR_ASSERT(action == svn_wc_conflict_action_delete 2811 || action == svn_wc_conflict_action_replace); 2812 2813 if (conflict_choice == svn_wc_conflict_choose_merged) 2814 { 2815 /* Whatever was moved is removed at its original location by the 2816 update. That must also remove the recording of the move, so 2817 we don't have to do anything here. */ 2818 2819 *did_resolve = TRUE; 2820 } 2821 else if (conflict_choice == svn_wc_conflict_choose_mine_conflict) 2822 { 2823 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, 2824 NULL, 2825 _("Tree conflict can only be " 2826 "resolved to 'working' state; " 2827 "'%s' is no longer moved"), 2828 svn_dirent_local_style(local_abspath, 2829 scratch_pool)); 2830 } 2831 } 2832 } 2833 2834 if (! *did_resolve) 2835 { 2836 if (conflict_choice != svn_wc_conflict_choose_merged) 2837 { 2838 /* For other tree conflicts, there is no way to pick 2839 * theirs-full or mine-full, etc. Throw an error if the 2840 * user expects us to be smarter than we really are. */ 2841 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, 2842 NULL, 2843 _("Tree conflict can only be " 2844 "resolved to 'working' state; " 2845 "'%s' not resolved"), 2846 svn_dirent_local_style(local_abspath, 2847 scratch_pool)); 2848 } 2849 else 2850 *did_resolve = TRUE; 2851 } 2852 2853 SVN_ERR_ASSERT(*did_resolve); 2854 2855 SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, FALSE, TRUE, 2856 NULL, scratch_pool)); 2857 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, 2858 scratch_pool)); 2859 return SVN_NO_ERROR; 2860} 2861 2862svn_error_t * 2863svn_wc__mark_resolved_text_conflict(svn_wc__db_t *db, 2864 const char *local_abspath, 2865 svn_cancel_func_t cancel_func, 2866 void *cancel_baton, 2867 apr_pool_t *scratch_pool) 2868{ 2869 svn_skel_t *work_items; 2870 svn_skel_t *conflict; 2871 2872 SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL, 2873 db, local_abspath, 2874 scratch_pool, scratch_pool)); 2875 2876 if (!conflict) 2877 return SVN_NO_ERROR; 2878 2879 SVN_ERR(build_text_conflict_resolve_items(&work_items, NULL, 2880 db, local_abspath, conflict, 2881 svn_wc_conflict_choose_merged, 2882 NULL, FALSE, NULL, 2883 cancel_func, cancel_baton, 2884 scratch_pool, scratch_pool)); 2885 2886 SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, TRUE, FALSE, FALSE, 2887 work_items, scratch_pool)); 2888 2889 return svn_error_trace(svn_wc__wq_run(db, local_abspath, 2890 cancel_func, cancel_baton, 2891 scratch_pool)); 2892} 2893 2894svn_error_t * 2895svn_wc__mark_resolved_prop_conflicts(svn_wc__db_t *db, 2896 const char *local_abspath, 2897 apr_pool_t *scratch_pool) 2898{ 2899 svn_boolean_t ignored_result; 2900 svn_skel_t *conflicts; 2901 2902 SVN_ERR(svn_wc__db_read_conflict(&conflicts, NULL, NULL, 2903 db, local_abspath, 2904 scratch_pool, scratch_pool)); 2905 2906 if (!conflicts) 2907 return SVN_NO_ERROR; 2908 2909 return svn_error_trace(resolve_prop_conflict_on_node( 2910 &ignored_result, 2911 db, local_abspath, conflicts, "", 2912 svn_wc_conflict_choose_merged, 2913 NULL, NULL, 2914 NULL, NULL, 2915 scratch_pool)); 2916} 2917 2918 2919/* Baton for conflict_status_walker */ 2920struct conflict_status_walker_baton 2921{ 2922 svn_wc__db_t *db; 2923 svn_boolean_t resolve_text; 2924 const char *resolve_prop; 2925 svn_boolean_t resolve_tree; 2926 svn_wc_conflict_choice_t conflict_choice; 2927 svn_wc_conflict_resolver_func2_t conflict_func; 2928 void *conflict_baton; 2929 svn_cancel_func_t cancel_func; 2930 void *cancel_baton; 2931 svn_wc_notify_func2_t notify_func; 2932 void *notify_baton; 2933 svn_boolean_t resolved_one; 2934 apr_hash_t *resolve_later; 2935}; 2936 2937/* Implements svn_wc_notify_func2_t to collect new conflicts caused by 2938 resolving a tree conflict. */ 2939static void 2940tree_conflict_collector(void *baton, 2941 const svn_wc_notify_t *notify, 2942 apr_pool_t *pool) 2943{ 2944 struct conflict_status_walker_baton *cswb = baton; 2945 2946 if (cswb->notify_func) 2947 cswb->notify_func(cswb->notify_baton, notify, pool); 2948 2949 if (cswb->resolve_later 2950 && (notify->action == svn_wc_notify_tree_conflict 2951 || notify->prop_state == svn_wc_notify_state_conflicted 2952 || notify->content_state == svn_wc_notify_state_conflicted)) 2953 { 2954 if (!svn_hash_gets(cswb->resolve_later, notify->path)) 2955 { 2956 const char *dup_path; 2957 2958 dup_path = apr_pstrdup(apr_hash_pool_get(cswb->resolve_later), 2959 notify->path); 2960 2961 svn_hash_sets(cswb->resolve_later, dup_path, dup_path); 2962 } 2963 } 2964} 2965 2966/* Implements svn_wc_status4_t to walk all conflicts to resolve. 2967 */ 2968static svn_error_t * 2969conflict_status_walker(void *baton, 2970 const char *local_abspath, 2971 const svn_wc_status3_t *status, 2972 apr_pool_t *scratch_pool) 2973{ 2974 struct conflict_status_walker_baton *cswb = baton; 2975 svn_wc__db_t *db = cswb->db; 2976 2977 const apr_array_header_t *conflicts; 2978 apr_pool_t *iterpool; 2979 int i; 2980 svn_boolean_t resolved = FALSE; 2981 svn_skel_t *conflict; 2982 2983 if (!status->conflicted) 2984 return SVN_NO_ERROR; 2985 2986 iterpool = svn_pool_create(scratch_pool); 2987 2988 SVN_ERR(svn_wc__read_conflicts(&conflicts, &conflict, 2989 db, local_abspath, 2990 (cswb->conflict_func != NULL) /* tmp files */, 2991 FALSE /* only tree conflicts */, 2992 scratch_pool, iterpool)); 2993 2994 for (i = 0; i < conflicts->nelts; i++) 2995 { 2996 const svn_wc_conflict_description2_t *cd; 2997 svn_boolean_t did_resolve; 2998 svn_wc_conflict_choice_t my_choice = cswb->conflict_choice; 2999 svn_wc_conflict_result_t *result = NULL; 3000 svn_skel_t *work_items; 3001 3002 cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *); 3003 3004 if ((cd->kind == svn_wc_conflict_kind_property 3005 && (!cswb->resolve_prop 3006 || (*cswb->resolve_prop != '\0' 3007 && strcmp(cswb->resolve_prop, cd->property_name) != 0))) 3008 || (cd->kind == svn_wc_conflict_kind_text && !cswb->resolve_text) 3009 || (cd->kind == svn_wc_conflict_kind_tree && !cswb->resolve_tree)) 3010 { 3011 continue; /* Easy out. Don't call resolver func and ignore result */ 3012 } 3013 3014 svn_pool_clear(iterpool); 3015 3016 if (my_choice == svn_wc_conflict_choose_unspecified) 3017 { 3018 if (!cswb->conflict_func) 3019 return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, 3020 _("No conflict-callback and no " 3021 "pre-defined conflict-choice provided")); 3022 3023 SVN_ERR(cswb->conflict_func(&result, cd, cswb->conflict_baton, 3024 iterpool, iterpool)); 3025 3026 my_choice = result->choice; 3027 } 3028 3029 3030 if (my_choice == svn_wc_conflict_choose_postpone) 3031 continue; 3032 3033 switch (cd->kind) 3034 { 3035 case svn_wc_conflict_kind_tree: 3036 SVN_ERR(resolve_tree_conflict_on_node(&did_resolve, 3037 db, 3038 local_abspath, conflict, 3039 my_choice, 3040 cswb->resolve_later, 3041 tree_conflict_collector, 3042 cswb, 3043 cswb->cancel_func, 3044 cswb->cancel_baton, 3045 iterpool)); 3046 3047 if (did_resolve) 3048 resolved = TRUE; 3049 break; 3050 3051 case svn_wc_conflict_kind_text: 3052 SVN_ERR(build_text_conflict_resolve_items( 3053 &work_items, 3054 &resolved, 3055 db, local_abspath, conflict, 3056 my_choice, 3057 result ? result->merged_file 3058 : NULL, 3059 result ? result->save_merged 3060 : FALSE, 3061 NULL /* merge_options */, 3062 cswb->cancel_func, 3063 cswb->cancel_baton, 3064 iterpool, iterpool)); 3065 3066 SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, 3067 TRUE, FALSE, FALSE, 3068 work_items, iterpool)); 3069 SVN_ERR(svn_wc__wq_run(db, local_abspath, 3070 cswb->cancel_func, cswb->cancel_baton, 3071 iterpool)); 3072 break; 3073 3074 case svn_wc_conflict_kind_property: 3075 SVN_ERR(resolve_prop_conflict_on_node(&did_resolve, 3076 db, 3077 local_abspath, 3078 conflict, 3079 cd->property_name, 3080 my_choice, 3081 result 3082 ? result->merged_file 3083 : NULL, 3084 result 3085 ? result->merged_value 3086 : NULL, 3087 cswb->cancel_func, 3088 cswb->cancel_baton, 3089 iterpool)); 3090 3091 if (did_resolve) 3092 resolved = TRUE; 3093 break; 3094 3095 default: 3096 /* We can't resolve other conflict types */ 3097 break; 3098 } 3099 } 3100 3101 /* Notify */ 3102 if (cswb->notify_func && resolved) 3103 cswb->notify_func(cswb->notify_baton, 3104 svn_wc_create_notify(local_abspath, 3105 svn_wc_notify_resolved, 3106 iterpool), 3107 iterpool); 3108 3109 if (resolved) 3110 cswb->resolved_one = TRUE; 3111 3112 svn_pool_destroy(iterpool); 3113 3114 return SVN_NO_ERROR; 3115} 3116 3117svn_error_t * 3118svn_wc__resolve_conflicts(svn_wc_context_t *wc_ctx, 3119 const char *local_abspath, 3120 svn_depth_t depth, 3121 svn_boolean_t resolve_text, 3122 const char *resolve_prop, 3123 svn_boolean_t resolve_tree, 3124 svn_wc_conflict_choice_t conflict_choice, 3125 svn_wc_conflict_resolver_func2_t conflict_func, 3126 void *conflict_baton, 3127 svn_cancel_func_t cancel_func, 3128 void *cancel_baton, 3129 svn_wc_notify_func2_t notify_func, 3130 void *notify_baton, 3131 apr_pool_t *scratch_pool) 3132{ 3133 svn_node_kind_t kind; 3134 svn_boolean_t conflicted; 3135 struct conflict_status_walker_baton cswb; 3136 apr_pool_t *iterpool = NULL; 3137 svn_error_t *err; 3138 3139 /* ### Just a versioned check? */ 3140 /* Conflicted is set to allow invoking on actual only nodes */ 3141 SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL, 3142 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 3143 NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, 3144 NULL, NULL, NULL, NULL, NULL, NULL, 3145 wc_ctx->db, local_abspath, 3146 scratch_pool, scratch_pool)); 3147 3148 /* When the implementation still used the entry walker, depth 3149 unknown was translated to infinity. */ 3150 if (kind != svn_node_dir) 3151 depth = svn_depth_empty; 3152 else if (depth == svn_depth_unknown) 3153 depth = svn_depth_infinity; 3154 3155 cswb.db = wc_ctx->db; 3156 cswb.resolve_text = resolve_text; 3157 cswb.resolve_prop = resolve_prop; 3158 cswb.resolve_tree = resolve_tree; 3159 cswb.conflict_choice = conflict_choice; 3160 3161 cswb.conflict_func = conflict_func; 3162 cswb.conflict_baton = conflict_baton; 3163 3164 cswb.cancel_func = cancel_func; 3165 cswb.cancel_baton = cancel_baton; 3166 3167 cswb.notify_func = notify_func; 3168 cswb.notify_baton = notify_baton; 3169 3170 cswb.resolved_one = FALSE; 3171 cswb.resolve_later = (depth != svn_depth_empty) 3172 ? apr_hash_make(scratch_pool) 3173 : NULL; 3174 3175 if (notify_func) 3176 notify_func(notify_baton, 3177 svn_wc_create_notify(local_abspath, 3178 svn_wc_notify_conflict_resolver_starting, 3179 scratch_pool), 3180 scratch_pool); 3181 3182 err = svn_wc_walk_status(wc_ctx, 3183 local_abspath, 3184 depth, 3185 FALSE /* get_all */, 3186 FALSE /* no_ignore */, 3187 TRUE /* ignore_text_mods */, 3188 NULL /* ignore_patterns */, 3189 conflict_status_walker, &cswb, 3190 cancel_func, cancel_baton, 3191 scratch_pool); 3192 3193 /* If we got new tree conflicts (or delayed conflicts) during the initial 3194 walk, we now walk them one by one as closure. */ 3195 while (!err && cswb.resolve_later && apr_hash_count(cswb.resolve_later)) 3196 { 3197 apr_hash_index_t *hi; 3198 svn_wc_status3_t *status = NULL; 3199 const char *tc_abspath = NULL; 3200 3201 if (iterpool) 3202 svn_pool_clear(iterpool); 3203 else 3204 iterpool = svn_pool_create(scratch_pool); 3205 3206 hi = apr_hash_first(scratch_pool, cswb.resolve_later); 3207 cswb.resolve_later = apr_hash_make(scratch_pool); 3208 cswb.resolved_one = FALSE; 3209 3210 for (; hi && !err; hi = apr_hash_next(hi)) 3211 { 3212 const char *relpath; 3213 svn_pool_clear(iterpool); 3214 3215 tc_abspath = apr_hash_this_key(hi); 3216 3217 if (cancel_func) 3218 SVN_ERR(cancel_func(cancel_baton)); 3219 3220 relpath = svn_dirent_skip_ancestor(local_abspath, 3221 tc_abspath); 3222 3223 if (!relpath 3224 || (depth >= svn_depth_empty 3225 && depth < svn_depth_infinity 3226 && strchr(relpath, '/'))) 3227 { 3228 continue; 3229 } 3230 3231 SVN_ERR(svn_wc_status3(&status, wc_ctx, tc_abspath, 3232 iterpool, iterpool)); 3233 3234 if (depth == svn_depth_files 3235 && status->kind == svn_node_dir) 3236 continue; 3237 3238 err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath, 3239 status, scratch_pool)); 3240 } 3241 3242 /* None of the remaining conflicts got resolved, and non did provide 3243 an error... 3244 3245 We can fix that if we disable the 'resolve_later' option... 3246 */ 3247 if (!cswb.resolved_one && !err && tc_abspath 3248 && apr_hash_count(cswb.resolve_later)) 3249 { 3250 /* Run the last resolve operation again. We still have status 3251 and tc_abspath for that one. */ 3252 3253 cswb.resolve_later = NULL; /* Produce proper error! */ 3254 3255 /* Recreate the error */ 3256 err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath, 3257 status, scratch_pool)); 3258 3259 SVN_ERR_ASSERT(err != NULL); 3260 3261 err = svn_error_createf( 3262 SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err, 3263 _("Unable to resolve pending conflict on '%s'"), 3264 svn_dirent_local_style(tc_abspath, scratch_pool)); 3265 break; 3266 } 3267 } 3268 3269 if (iterpool) 3270 svn_pool_destroy(iterpool); 3271 3272 if (err && err->apr_err != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE) 3273 err = svn_error_createf( 3274 SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err, 3275 _("Unable to resolve conflicts on '%s'"), 3276 svn_dirent_local_style(local_abspath, scratch_pool)); 3277 3278 SVN_ERR(err); 3279 3280 if (notify_func) 3281 notify_func(notify_baton, 3282 svn_wc_create_notify(local_abspath, 3283 svn_wc_notify_conflict_resolver_done, 3284 scratch_pool), 3285 scratch_pool); 3286 3287 return SVN_NO_ERROR; 3288} 3289 3290svn_error_t * 3291svn_wc_resolved_conflict5(svn_wc_context_t *wc_ctx, 3292 const char *local_abspath, 3293 svn_depth_t depth, 3294 svn_boolean_t resolve_text, 3295 const char *resolve_prop, 3296 svn_boolean_t resolve_tree, 3297 svn_wc_conflict_choice_t conflict_choice, 3298 svn_cancel_func_t cancel_func, 3299 void *cancel_baton, 3300 svn_wc_notify_func2_t notify_func, 3301 void *notify_baton, 3302 apr_pool_t *scratch_pool) 3303{ 3304 return svn_error_trace(svn_wc__resolve_conflicts(wc_ctx, local_abspath, 3305 depth, resolve_text, 3306 resolve_prop, resolve_tree, 3307 conflict_choice, 3308 NULL, NULL, 3309 cancel_func, cancel_baton, 3310 notify_func, notify_baton, 3311 scratch_pool)); 3312} 3313 3314/* Constructor for the result-structure returned by conflict callbacks. */ 3315svn_wc_conflict_result_t * 3316svn_wc_create_conflict_result(svn_wc_conflict_choice_t choice, 3317 const char *merged_file, 3318 apr_pool_t *pool) 3319{ 3320 svn_wc_conflict_result_t *result = apr_pcalloc(pool, sizeof(*result)); 3321 result->choice = choice; 3322 result->merged_file = apr_pstrdup(pool, merged_file); 3323 result->save_merged = FALSE; 3324 3325 /* If we add more fields to svn_wc_conflict_result_t, add them here. */ 3326 3327 return result; 3328} 3329