conflict-callbacks.c revision 362181
1/* 2 * conflict-callbacks.c: conflict resolution callbacks specific to the 3 * commandline client. 4 * 5 * ==================================================================== 6 * Licensed to the Apache Software Foundation (ASF) under one 7 * or more contributor license agreements. See the NOTICE file 8 * distributed with this work for additional information 9 * regarding copyright ownership. The ASF licenses this file 10 * to you under the Apache License, Version 2.0 (the 11 * "License"); you may not use this file except in compliance 12 * with the License. You may obtain a copy of the License at 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 16 * Unless required by applicable law or agreed to in writing, 17 * software distributed under the License is distributed on an 18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 * KIND, either express or implied. See the License for the 20 * specific language governing permissions and limitations 21 * under the License. 22 * ==================================================================== 23 */ 24 25#include <apr_xlate.h> /* for APR_LOCALE_CHARSET */ 26 27#define APR_WANT_STRFUNC 28#include <apr_want.h> 29 30#include "svn_hash.h" 31#include "svn_cmdline.h" 32#include "svn_client.h" 33#include "svn_dirent_uri.h" 34#include "svn_types.h" 35#include "svn_pools.h" 36#include "svn_sorts.h" 37#include "svn_utf.h" 38 39#include "cl.h" 40#include "cl-conflicts.h" 41 42#include "private/svn_cmdline_private.h" 43 44#include "svn_private_config.h" 45 46#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0]))) 47 48 49 50svn_cl__accept_t 51svn_cl__accept_from_word(const char *word) 52{ 53 /* Shorthand options are consistent with svn_cl__conflict_handler(). */ 54 if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0 55 || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0) 56 return svn_cl__accept_postpone; 57 if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0) 58 /* ### shorthand? */ 59 return svn_cl__accept_base; 60 if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0) 61 /* ### shorthand? */ 62 return svn_cl__accept_working; 63 if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0 64 || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0) 65 return svn_cl__accept_mine_conflict; 66 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0 67 || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0) 68 return svn_cl__accept_theirs_conflict; 69 if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0 70 || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0) 71 return svn_cl__accept_mine_full; 72 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0 73 || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0) 74 return svn_cl__accept_theirs_full; 75 if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0 76 || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0) 77 return svn_cl__accept_edit; 78 if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0 79 || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0) 80 return svn_cl__accept_launch; 81 if (strcmp(word, SVN_CL__ACCEPT_RECOMMENDED) == 0 82 || strcmp(word, "r") == 0) 83 return svn_cl__accept_recommended; 84 /* word is an invalid action. */ 85 return svn_cl__accept_invalid; 86} 87 88 89/* Print on stdout a diff that shows incoming conflicting changes 90 * corresponding to the conflict described in CONFLICT. */ 91static svn_error_t * 92show_diff(svn_client_conflict_t *conflict, 93 const char *merged_abspath, 94 const char *path_prefix, 95 svn_cancel_func_t cancel_func, 96 void *cancel_baton, 97 apr_pool_t *pool) 98{ 99 const char *path1, *path2; 100 const char *label1, *label2; 101 svn_diff_t *diff; 102 svn_stream_t *output; 103 svn_diff_file_options_t *options; 104 const char *my_abspath; 105 const char *their_abspath; 106 107 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, NULL, 108 &their_abspath, 109 conflict, pool, pool)); 110 if (merged_abspath) 111 { 112 /* For conflicts recorded by the 'merge' operation, show a diff between 113 * 'mine' (the working version of the file as it appeared before the 114 * 'merge' operation was run) and 'merged' (the version of the file 115 * as it appears after the merge operation). 116 * 117 * For conflicts recorded by the 'update' and 'switch' operations, 118 * show a diff between 'theirs' (the new pristine version of the 119 * file) and 'merged' (the version of the file as it appears with 120 * local changes merged with the new pristine version). 121 * 122 * This way, the diff is always minimal and clearly identifies changes 123 * brought into the working copy by the update/switch/merge operation. */ 124 if (svn_client_conflict_get_operation(conflict) == svn_wc_operation_merge) 125 { 126 path1 = my_abspath; 127 label1 = _("MINE"); 128 } 129 else 130 { 131 path1 = their_abspath; 132 label1 = _("THEIRS"); 133 } 134 path2 = merged_abspath; 135 label2 = _("MERGED"); 136 } 137 else 138 { 139 /* There's no merged file, but we can show the 140 difference between mine and theirs. */ 141 path1 = their_abspath; 142 label1 = _("THEIRS"); 143 path2 = my_abspath; 144 label2 = _("MINE"); 145 } 146 147 label1 = apr_psprintf(pool, "%s\t- %s", 148 svn_cl__local_style_skip_ancestor( 149 path_prefix, path1, pool), label1); 150 label2 = apr_psprintf(pool, "%s\t- %s", 151 svn_cl__local_style_skip_ancestor( 152 path_prefix, path2, pool), label2); 153 154 options = svn_diff_file_options_create(pool); 155 options->ignore_eol_style = TRUE; 156 SVN_ERR(svn_stream_for_stdout(&output, pool)); 157 SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2, 158 options, pool)); 159 return svn_diff_file_output_unified4(output, diff, 160 path1, path2, 161 label1, label2, 162 APR_LOCALE_CHARSET, 163 NULL, 164 options->show_c_function, 165 options->context_size, 166 cancel_func, cancel_baton, 167 pool); 168} 169 170 171/* Print on stdout just the conflict hunks of a diff among the 'base', 'their' 172 * and 'my' files of CONFLICT. */ 173static svn_error_t * 174show_conflicts(svn_client_conflict_t *conflict, 175 svn_cancel_func_t cancel_func, 176 void *cancel_baton, 177 apr_pool_t *pool) 178{ 179 svn_diff_t *diff; 180 svn_stream_t *output; 181 svn_diff_file_options_t *options; 182 const char *base_abspath; 183 const char *my_abspath; 184 const char *their_abspath; 185 186 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, 187 &base_abspath, &their_abspath, 188 conflict, pool, pool)); 189 options = svn_diff_file_options_create(pool); 190 options->ignore_eol_style = TRUE; 191 SVN_ERR(svn_stream_for_stdout(&output, pool)); 192 SVN_ERR(svn_diff_file_diff3_2(&diff, base_abspath, my_abspath, their_abspath, 193 options, pool)); 194 /* ### Consider putting the markers/labels from 195 ### svn_wc__merge_internal in the conflict description. */ 196 return svn_diff_file_output_merge3( 197 output, diff, base_abspath, my_abspath, their_abspath, 198 _("||||||| ORIGINAL"), 199 _("<<<<<<< MINE (select with 'mc')"), 200 _(">>>>>>> THEIRS (select with 'tc')"), 201 "=======", 202 svn_diff_conflict_display_only_conflicts, 203 cancel_func, 204 cancel_baton, 205 pool); 206} 207 208/* Perform a 3-way merge of the conflicting values of a property, 209 * and write the result to the OUTPUT stream. 210 * 211 * If MERGED_PROPVAL is non-NULL, use it as 'my' version instead of 212 * MY_ABSPATH. 213 * 214 * Assume the values are printable UTF-8 text. 215 */ 216static svn_error_t * 217merge_prop_conflict(svn_stream_t *output, 218 const svn_string_t *base_propval, 219 const svn_string_t *my_propval, 220 const svn_string_t *their_propval, 221 const svn_string_t *merged_propval, 222 svn_cancel_func_t cancel_func, 223 void *cancel_baton, 224 apr_pool_t *pool) 225{ 226 svn_diff_file_options_t *options = svn_diff_file_options_create(pool); 227 svn_diff_t *diff; 228 229 /* If any of the property values is missing, use an empty value instead 230 * for the purpose of showing a diff. */ 231 if (base_propval == NULL) 232 base_propval = svn_string_create_empty(pool); 233 if (my_propval == NULL) 234 my_propval = svn_string_create_empty(pool); 235 if (their_propval == NULL) 236 their_propval = svn_string_create_empty(pool); 237 238 options->ignore_eol_style = TRUE; 239 SVN_ERR(svn_diff_mem_string_diff3(&diff, base_propval, 240 merged_propval ? 241 merged_propval : my_propval, 242 their_propval, options, pool)); 243 SVN_ERR(svn_diff_mem_string_output_merge3( 244 output, diff, base_propval, 245 merged_propval ? merged_propval : my_propval, their_propval, 246 _("||||||| ORIGINAL"), 247 _("<<<<<<< MINE"), 248 _(">>>>>>> THEIRS"), 249 "=======", 250 svn_diff_conflict_display_modified_original_latest, 251 cancel_func, 252 cancel_baton, 253 pool)); 254 255 return SVN_NO_ERROR; 256} 257 258/* Display the conflicting values of a property as a 3-way diff. 259 * 260 * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of 261 * DESC->MY_ABSPATH. 262 * 263 * Assume the values are printable UTF-8 text. 264 */ 265static svn_error_t * 266show_prop_conflict(const svn_string_t *base_propval, 267 const svn_string_t *my_propval, 268 const svn_string_t *their_propval, 269 const svn_string_t *merged_propval, 270 svn_cancel_func_t cancel_func, 271 void *cancel_baton, 272 apr_pool_t *pool) 273{ 274 svn_stream_t *output; 275 276 SVN_ERR(svn_stream_for_stdout(&output, pool)); 277 SVN_ERR(merge_prop_conflict(output, base_propval, my_propval, their_propval, 278 merged_propval, cancel_func, cancel_baton, pool)); 279 280 return SVN_NO_ERROR; 281} 282 283/* Run an external editor, passing it the MERGED_ABSPATH, or, if the 284 * 'merged' file is null, return an error. The tool to use is determined by 285 * B->editor_cmd, B->config and environment variables; see 286 * svn_cl__edit_file_externally() for details. 287 * 288 * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not 289 * configured or cannot run, do not touch *PERFORMED_EDIT, report the error 290 * on stderr, and return SVN_NO_ERROR; if any other error is encountered, 291 * return that error. */ 292static svn_error_t * 293open_editor(svn_boolean_t *performed_edit, 294 const char *merged_abspath, 295 const char *editor_cmd, 296 apr_hash_t *config, 297 apr_pool_t *pool) 298{ 299 svn_error_t *err; 300 301 if (merged_abspath) 302 { 303 err = svn_cmdline__edit_file_externally(merged_abspath, editor_cmd, 304 config, pool); 305 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR || 306 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 307 { 308 char buf[1024]; 309 const char *message; 310 311 message = svn_err_best_message(err, buf, sizeof(buf)); 312 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", message)); 313 svn_error_clear(err); 314 } 315 else if (err) 316 return svn_error_trace(err); 317 else 318 *performed_edit = TRUE; 319 } 320 else 321 SVN_ERR(svn_cmdline_fprintf(stderr, pool, 322 _("Invalid option; there's no " 323 "merged version to edit.\n\n"))); 324 325 return SVN_NO_ERROR; 326} 327 328/* Run an external editor on the merged property value with conflict markers. 329 * Return the edited result in *MERGED_PROPVAL. 330 * If the edit is aborted, set *MERGED_ABSPATH and *MERGED_PROPVAL to NULL. 331 * The tool to use is determined by B->editor_cmd, B->config and 332 * environment variables; see svn_cl__edit_file_externally() for details. */ 333static svn_error_t * 334edit_prop_conflict(const svn_string_t **merged_propval, 335 const svn_string_t *base_propval, 336 const svn_string_t *my_propval, 337 const svn_string_t *their_propval, 338 const char *editor_cmd, 339 apr_hash_t *config, 340 svn_cmdline_prompt_baton_t *pb, 341 apr_pool_t *result_pool, 342 apr_pool_t *scratch_pool) 343{ 344 const char *file_path; 345 svn_boolean_t performed_edit = FALSE; 346 svn_stream_t *merged_prop; 347 348 SVN_ERR(svn_stream_open_unique(&merged_prop, &file_path, NULL, 349 svn_io_file_del_on_pool_cleanup, 350 scratch_pool, scratch_pool)); 351 SVN_ERR(merge_prop_conflict(merged_prop, base_propval, my_propval, 352 their_propval, NULL, 353 pb->cancel_func, 354 pb->cancel_baton, 355 scratch_pool)); 356 SVN_ERR(svn_stream_close(merged_prop)); 357 SVN_ERR(open_editor(&performed_edit, file_path, editor_cmd, 358 config, scratch_pool)); 359 if (performed_edit && merged_propval) 360 { 361 svn_stringbuf_t *buf; 362 363 SVN_ERR(svn_stringbuf_from_file2(&buf, file_path, scratch_pool)); 364 *merged_propval = svn_string_create_from_buf(buf, result_pool); 365 } 366 367 return SVN_NO_ERROR; 368} 369 370/* Maximum line length for the prompt string. */ 371#define MAX_PROMPT_WIDTH 70 372 373/* Description of a resolver option. 374 * Resolver options are used to build the resolver's conflict prompt. 375 * The user types a code to select the corresponding conflict resolution option. 376 * Some resolver options have a corresponding --accept argument. */ 377typedef struct resolver_option_t 378{ 379 const char *code; /* one or two characters */ 380 svn_client_conflict_option_id_t choice; 381 /* or ..._undefined if not from libsvn_client */ 382 const char *accept_arg; /* --accept option argument (NOT localized) */ 383} resolver_option_t; 384 385typedef struct client_option_t 386{ 387 const char *code; /* one or two characters */ 388 const char *label; /* label in prompt (localized) */ 389 const char *long_desc; /* longer description (localized) */ 390 svn_client_conflict_option_id_t choice; 391 /* or ..._undefined if not from libsvn_client */ 392 const char *accept_arg; /* --accept option argument (NOT localized) */ 393 svn_boolean_t is_recommended; /* if TRUE, try this option before prompting */ 394} client_option_t; 395 396/* Resolver options for conflict options offered by libsvn_client. */ 397static const resolver_option_t builtin_resolver_options[] = 398{ 399 { "r", svn_client_conflict_option_merged_text, 400 SVN_CL__ACCEPT_WORKING }, 401 { "mc", svn_client_conflict_option_working_text_where_conflicted, 402 SVN_CL__ACCEPT_MINE_CONFLICT }, 403 { "tc", svn_client_conflict_option_incoming_text_where_conflicted, 404 SVN_CL__ACCEPT_THEIRS_CONFLICT }, 405 { "mf", svn_client_conflict_option_working_text, 406 SVN_CL__ACCEPT_MINE_FULL}, 407 { "tf", svn_client_conflict_option_incoming_text, 408 SVN_CL__ACCEPT_THEIRS_FULL }, 409 { "p", svn_client_conflict_option_postpone, 410 SVN_CL__ACCEPT_POSTPONE }, 411 412 /* This option resolves a tree conflict to the current working copy state. */ 413 { "r", svn_client_conflict_option_accept_current_wc_state, 414 SVN_CL__ACCEPT_WORKING }, 415 416 /* These options use the same code since they only occur in 417 * distinct conflict scenarios. */ 418 { "u", svn_client_conflict_option_update_move_destination }, 419 { "u", svn_client_conflict_option_update_any_moved_away_children }, 420 421 /* Options for incoming add vs local add. */ 422 { "i", svn_client_conflict_option_incoming_add_ignore }, 423 424 /* Options for incoming file add vs local file add upon merge. */ 425 { "m", svn_client_conflict_option_incoming_added_file_text_merge }, 426 { "M", svn_client_conflict_option_incoming_added_file_replace_and_merge }, 427 428 /* Options for incoming dir add vs local dir add upon merge. */ 429 { "m", svn_client_conflict_option_incoming_added_dir_merge }, 430 { "R", svn_client_conflict_option_incoming_added_dir_replace }, 431 { "M", svn_client_conflict_option_incoming_added_dir_replace_and_merge }, 432 433 /* Options for incoming delete vs any. */ 434 { "i", svn_client_conflict_option_incoming_delete_ignore }, 435 { "a", svn_client_conflict_option_incoming_delete_accept }, 436 437 /* Options for incoming move vs local edit. */ 438 { "m", svn_client_conflict_option_incoming_move_file_text_merge }, 439 { "m", svn_client_conflict_option_incoming_move_dir_merge }, 440 441 /* Options for local move vs incoming edit. */ 442 { "m", svn_client_conflict_option_local_move_file_text_merge }, 443 { "m", svn_client_conflict_option_local_move_dir_merge }, 444 445 /* Options for local missing vs incoming edit. */ 446 { "m", svn_client_conflict_option_sibling_move_file_text_merge }, 447 { "m", svn_client_conflict_option_sibling_move_dir_merge }, 448 449 /* Options for incoming move vs local move. */ 450 { "m", svn_client_conflict_option_both_moved_file_merge }, 451 { "M", svn_client_conflict_option_both_moved_file_move_merge }, 452 { "m", svn_client_conflict_option_both_moved_dir_merge }, 453 { "M", svn_client_conflict_option_both_moved_dir_move_merge }, 454 455 { NULL } 456}; 457 458/* Extra resolver options offered by 'svn' for any conflict. */ 459static const client_option_t extra_resolver_options[] = 460{ 461 /* Translators: keep long_desc below 70 characters (wrap with a left 462 margin of 9 spaces if needed) */ 463 { "q", N_("Quit resolution"), N_("postpone all remaining conflicts"), 464 svn_client_conflict_option_postpone }, 465 { NULL } 466}; 467 468 469/* Additional resolver options offered by 'svn' for a text conflict. */ 470static const client_option_t extra_resolver_options_text[] = 471{ 472 /* Translators: keep long_desc below 70 characters (wrap with a left 473 margin of 9 spaces if needed) */ 474 { "e", N_("Edit file"), N_("change merged file in an editor"), 475 svn_client_conflict_option_undefined, 476 SVN_CL__ACCEPT_EDIT }, 477 { "df", N_("Show diff"), N_("show all changes made to merged file"), 478 svn_client_conflict_option_undefined}, 479 { "dc", N_("Display conflict"), N_("show all conflicts " 480 "(ignoring merged version)"), 481 svn_client_conflict_option_undefined }, 482 { "m", N_("Merge"), N_("use merge tool to resolve conflict"), 483 svn_client_conflict_option_undefined }, 484 { "l", N_("Launch tool"), N_("launch external merge tool to resolve " 485 "conflict"), 486 svn_client_conflict_option_undefined, 487 SVN_CL__ACCEPT_LAUNCH }, 488 { "i", N_("Internal merge tool"), N_("use built-in merge tool to " 489 "resolve conflict"), 490 svn_client_conflict_option_undefined }, 491 { "s", N_("Show all options"), N_("show this list (also 'h', '?')"), 492 svn_client_conflict_option_undefined }, 493 { NULL } 494}; 495 496/* Additional resolver options offered by 'svn' for a property conflict. */ 497static const client_option_t extra_resolver_options_prop[] = 498{ 499 /* Translators: keep long_desc below 70 characters (wrap with a left 500 margin of 9 spaces if needed) */ 501 { "dc", N_("Display conflict"), N_("show conflicts in this property"), 502 svn_client_conflict_option_undefined }, 503 { "e", N_("Edit property"), N_("change merged property value in an " 504 "editor"), 505 svn_client_conflict_option_undefined, 506 SVN_CL__ACCEPT_EDIT }, 507 { "h", N_("Help"), N_("show this help (also '?')"), 508 svn_client_conflict_option_undefined }, 509 { NULL } 510}; 511 512/* Additional resolver options offered by 'svn' for a tree conflict. */ 513static const client_option_t extra_resolver_options_tree[] = 514{ 515 /* Translators: keep long_desc below 70 characters (wrap with a left 516 margin of 9 spaces if needed) */ 517 { "d", N_("Set repository move destination path"), 518 N_("pick repository move target from list of possible targets"), 519 svn_client_conflict_option_undefined }, 520 521 { "w", N_("Set working copy move destination path"), 522 N_("pick working copy move target from list of possible targets"), 523 svn_client_conflict_option_undefined }, 524 525 { "h", N_("Help"), N_("show this help (also '?')"), 526 svn_client_conflict_option_undefined }, 527 528 { NULL } 529}; 530 531 532/* Return a pointer to the option description in OPTIONS matching the 533 * one- or two-character OPTION_CODE. Return NULL if not found. */ 534static const client_option_t * 535find_option(const apr_array_header_t *options, 536 const char *option_code) 537{ 538 int i; 539 540 for (i = 0; i < options->nelts; i++) 541 { 542 const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *); 543 544 /* Ignore code "" (blank lines) which is not a valid answer. */ 545 if (opt->code[0] && strcmp(opt->code, option_code) == 0) 546 return opt; 547 } 548 return NULL; 549} 550 551/* Find the first recommended option in OPTIONS. */ 552static const client_option_t * 553find_recommended_option(const apr_array_header_t *options) 554{ 555 int i; 556 557 for (i = 0; i < options->nelts; i++) 558 { 559 const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *); 560 561 /* Ignore code "" (blank lines) which is not a valid answer. */ 562 if (opt->code[0] && opt->is_recommended) 563 return opt; 564 } 565 return NULL; 566} 567 568/* Return a pointer to the client_option_t in OPTIONS matching the ID of 569 * conflict option BUILTIN_OPTION. @a out will be set to NULL if the 570 * option was not found. */ 571static svn_error_t * 572find_option_by_builtin(client_option_t **out, 573 svn_client_conflict_t *conflict, 574 const resolver_option_t *options, 575 svn_client_conflict_option_t *builtin_option, 576 apr_pool_t *result_pool, 577 apr_pool_t *scratch_pool) 578{ 579 const resolver_option_t *opt; 580 svn_client_conflict_option_id_t id; 581 svn_client_conflict_option_id_t recommended_id; 582 583 id = svn_client_conflict_option_get_id(builtin_option); 584 recommended_id = svn_client_conflict_get_recommended_option_id(conflict); 585 586 for (opt = options; opt->code; opt++) 587 { 588 if (opt->choice == id) 589 { 590 client_option_t *client_opt; 591 592 client_opt = apr_pcalloc(result_pool, sizeof(*client_opt)); 593 client_opt->choice = id; 594 client_opt->code = opt->code; 595 client_opt->label = svn_client_conflict_option_get_label( 596 builtin_option, 597 result_pool); 598 client_opt->long_desc = svn_client_conflict_option_get_description( 599 builtin_option, 600 result_pool); 601 client_opt->accept_arg = opt->accept_arg; 602 client_opt->is_recommended = 603 (recommended_id != svn_client_conflict_option_unspecified && 604 id == recommended_id); 605 606 *out = client_opt; 607 608 return SVN_NO_ERROR; 609 } 610 } 611 612 *out = NULL; 613 614 return SVN_NO_ERROR; 615} 616 617/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is 618 * non-null, select only the options whose codes are mentioned in it. */ 619static const char * 620prompt_string(const apr_array_header_t *options, 621 const char *const *option_codes, 622 apr_pool_t *pool) 623{ 624 const char *result = _("Select:"); 625 int left_margin = svn_utf_cstring_utf8_width(result); 626 const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, ""); 627 int this_line_len = left_margin; 628 svn_boolean_t first = TRUE; 629 int i = 0; 630 631 while (1) 632 { 633 const client_option_t *opt; 634 const char *s; 635 int slen; 636 637 if (option_codes) 638 { 639 if (! *option_codes) 640 break; 641 opt = find_option(options, *option_codes++); 642 if (opt == NULL) 643 continue; 644 } 645 else 646 { 647 if (i >= options->nelts) 648 break; 649 opt = APR_ARRAY_IDX(options, i, client_option_t *); 650 i++; 651 } 652 653 if (! first) 654 result = apr_pstrcat(pool, result, ",", SVN_VA_NULL); 655 s = apr_psprintf(pool, " (%s) %s", opt->code, 656 opt->label ? opt->label : opt->long_desc); 657 slen = svn_utf_cstring_utf8_width(s); 658 /* Break the line if adding the next option would make it too long */ 659 if (this_line_len + slen > MAX_PROMPT_WIDTH) 660 { 661 result = apr_pstrcat(pool, result, line_sep, SVN_VA_NULL); 662 this_line_len = left_margin; 663 } 664 result = apr_pstrcat(pool, result, s, SVN_VA_NULL); 665 this_line_len += slen; 666 first = FALSE; 667 } 668 return apr_pstrcat(pool, result, ": ", SVN_VA_NULL); 669} 670 671/* Return a help string listing the OPTIONS. */ 672static svn_error_t * 673help_string(const char **result, 674 const apr_array_header_t *options, 675 apr_pool_t *pool) 676{ 677 apr_pool_t *iterpool; 678 int i; 679 680 *result = ""; 681 iterpool = svn_pool_create(pool); 682 for (i = 0; i < options->nelts; i++) 683 { 684 const client_option_t *opt; 685 svn_pool_clear(iterpool); 686 687 opt = APR_ARRAY_IDX(options, i, 688 client_option_t *); 689 690 /* Append a line describing OPT, or a blank line if its code is "". */ 691 if (opt->code[0]) 692 { 693 const char *s = apr_psprintf(pool, " (%s)", opt->code); 694 695 if (opt->accept_arg) 696 *result = apr_psprintf(pool, "%s%-6s - %s [%s]\n", 697 *result, s, opt->long_desc, 698 opt->accept_arg); 699 else 700 *result = apr_psprintf(pool, "%s%-6s - %s\n", *result, s, 701 opt->long_desc); 702 } 703 else 704 { 705 *result = apr_pstrcat(pool, *result, "\n", SVN_VA_NULL); 706 } 707 } 708 svn_pool_destroy(iterpool); 709 *result = apr_pstrcat(pool, *result, 710 _("Words in square brackets are the corresponding " 711 "--accept option arguments.\n"), 712 SVN_VA_NULL); 713 return SVN_NO_ERROR; 714} 715 716/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed 717 * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen 718 * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to 719 * NULL if the answer was not one of them. 720 * 721 * If the answer is the (globally recognized) 'help' option, then display 722 * CONFLICT_DESCRIPTION (if not NULL) and help (on stderr) and return with 723 * *OPT == NULL. 724 */ 725static svn_error_t * 726prompt_user(const client_option_t **opt, 727 const apr_array_header_t *conflict_options, 728 const char *const *options_to_show, 729 const char *conflict_description, 730 void *prompt_baton, 731 apr_pool_t *scratch_pool) 732{ 733 const char *prompt 734 = prompt_string(conflict_options, options_to_show, scratch_pool); 735 const char *answer; 736 737 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool)); 738 if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0) 739 { 740 const char *helpstr; 741 742 if (conflict_description) 743 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", 744 conflict_description)); 745 SVN_ERR(help_string(&helpstr, conflict_options, scratch_pool)); 746 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", helpstr)); 747 *opt = NULL; 748 } 749 else 750 { 751 *opt = find_option(conflict_options, answer); 752 if (! *opt) 753 { 754 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 755 _("Unrecognized option.\n\n"))); 756 } 757 } 758 return SVN_NO_ERROR; 759} 760 761/* Set *OPTIONS to an array of resolution options for CONFLICT. */ 762static svn_error_t * 763build_text_conflict_options(apr_array_header_t **options, 764 svn_client_conflict_t *conflict, 765 svn_client_ctx_t *ctx, 766 svn_boolean_t is_binary, 767 apr_pool_t *result_pool, 768 apr_pool_t *scratch_pool) 769{ 770 const client_option_t *o; 771 apr_array_header_t *builtin_options; 772 int nopt; 773 int i; 774 apr_pool_t *iterpool; 775 776 SVN_ERR(svn_client_conflict_text_get_resolution_options(&builtin_options, 777 conflict, ctx, 778 scratch_pool, 779 scratch_pool)); 780 nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options); 781 if (!is_binary) 782 nopt += ARRAY_LEN(extra_resolver_options_text); 783 *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *)); 784 785 iterpool = svn_pool_create(scratch_pool); 786 for (i = 0; i < builtin_options->nelts; i++) 787 { 788 client_option_t *opt; 789 svn_client_conflict_option_t *builtin_option; 790 791 svn_pool_clear(iterpool); 792 builtin_option = APR_ARRAY_IDX(builtin_options, i, 793 svn_client_conflict_option_t *); 794 SVN_ERR(find_option_by_builtin(&opt, conflict, 795 builtin_resolver_options, 796 builtin_option, 797 result_pool, 798 iterpool)); 799 if (opt == NULL) 800 continue; /* ### unknown option -- assign a code dynamically? */ 801 802 APR_ARRAY_PUSH(*options, client_option_t *) = opt; 803 } 804 805 for (o = extra_resolver_options; o->code; o++) 806 APR_ARRAY_PUSH(*options, const client_option_t *) = o; 807 if (!is_binary) 808 { 809 for (o = extra_resolver_options_text; o->code; o++) 810 APR_ARRAY_PUSH(*options, const client_option_t *) = o; 811 } 812 813 svn_pool_destroy(iterpool); 814 815 return SVN_NO_ERROR; 816} 817 818/* Mark CONFLICT as resolved to resolution option with ID OPTION_ID. 819 * If TEXT_CONFLICTED is true, resolve text conflicts described by CONFLICT. 820 * IF PROPNAME is not NULL, mark the conflict in the specified property as 821 * resolved. If PROPNAME is "", mark all property conflicts described by 822 * CONFLICT as resolved. 823 * If TREE_CONFLICTED is true, resolve tree conflicts described by CONFLICT. 824 * Adjust CONFLICT_STATS as necessary (PATH_PREFIX is needed for this step). */ 825static svn_error_t * 826mark_conflict_resolved(svn_client_conflict_t *conflict, 827 svn_client_conflict_option_id_t option_id, 828 svn_boolean_t text_conflicted, 829 const char *propname, 830 svn_boolean_t tree_conflicted, 831 const char *path_prefix, 832 svn_cl__conflict_stats_t *conflict_stats, 833 svn_client_ctx_t *ctx, 834 apr_pool_t *scratch_pool) 835{ 836 const char *local_relpath 837 = svn_cl__local_style_skip_ancestor( 838 path_prefix, svn_client_conflict_get_local_abspath(conflict), 839 scratch_pool); 840 841 if (text_conflicted) 842 { 843 SVN_ERR(svn_client_conflict_text_resolve_by_id(conflict, option_id, 844 ctx, scratch_pool)); 845 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, 846 svn_wc_conflict_kind_text); 847 } 848 849 if (propname) 850 { 851 SVN_ERR(svn_client_conflict_prop_resolve_by_id(conflict, propname, 852 option_id, ctx, 853 scratch_pool)); 854 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, 855 svn_wc_conflict_kind_property); 856 } 857 858 if (tree_conflicted) 859 { 860 SVN_ERR(svn_client_conflict_tree_resolve_by_id(conflict, option_id, 861 ctx, scratch_pool)); 862 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, 863 svn_wc_conflict_kind_tree); 864 } 865 866 return SVN_NO_ERROR; 867} 868 869/* Ask the user what to do about the text conflict described by CONFLICT 870 * and either resolve the conflict accordingly or postpone resolution. 871 * SCRATCH_POOL is used for temporary allocations. */ 872static svn_error_t * 873handle_text_conflict(svn_boolean_t *resolved, 874 svn_boolean_t *postponed, 875 svn_boolean_t *quit, 876 svn_boolean_t *printed_description, 877 svn_client_conflict_t *conflict, 878 const char *path_prefix, 879 svn_cmdline_prompt_baton_t *pb, 880 const char *editor_cmd, 881 apr_hash_t *config, 882 svn_cl__conflict_stats_t *conflict_stats, 883 svn_client_ctx_t *ctx, 884 apr_pool_t *scratch_pool) 885{ 886 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 887 svn_boolean_t diff_allowed = FALSE; 888 /* Have they done something that might have affected the merged file? */ 889 svn_boolean_t performed_edit = FALSE; 890 /* Have they done *something* (edit, look at diff, etc) to 891 give them a rational basis for choosing (r)esolved? */ 892 svn_boolean_t knows_something = FALSE; 893 const char *local_relpath; 894 const char *local_abspath = svn_client_conflict_get_local_abspath(conflict); 895 const char *mime_type = svn_client_conflict_text_get_mime_type(conflict); 896 svn_boolean_t is_binary = mime_type ? svn_mime_type_is_binary(mime_type) 897 : FALSE; 898 const char *base_abspath; 899 const char *my_abspath; 900 const char *their_abspath; 901 const char *merged_abspath = svn_client_conflict_get_local_abspath(conflict); 902 apr_array_header_t *text_conflict_options; 903 svn_client_conflict_option_id_t option_id; 904 905 option_id = svn_client_conflict_option_unspecified; 906 907 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, 908 &base_abspath, &their_abspath, 909 conflict, scratch_pool, 910 scratch_pool)); 911 912 local_relpath = svn_cl__local_style_skip_ancestor(path_prefix, 913 local_abspath, 914 scratch_pool); 915 916 if (!*printed_description) 917 { 918 if (is_binary) 919 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 920 _("Merge conflict discovered in binary " 921 "file '%s'.\n"), 922 local_relpath)); 923 else 924 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 925 _("Merge conflict discovered in file '%s'.\n"), 926 local_relpath)); 927 *printed_description = TRUE; 928 } 929 930 /* ### TODO This whole feature availability check is grossly outdated. 931 DIFF_ALLOWED needs either to be redefined or to go away. 932 */ 933 934 /* Diffing can happen between base and merged, to show conflict 935 markers to the user (this is the typical 3-way merge 936 scenario), or if no base is available, we can show a diff 937 between mine and theirs. */ 938 if (!is_binary && 939 ((merged_abspath && base_abspath) 940 || (!base_abspath && my_abspath && their_abspath))) 941 diff_allowed = TRUE; 942 943 SVN_ERR(build_text_conflict_options(&text_conflict_options, conflict, ctx, 944 is_binary, scratch_pool, scratch_pool)); 945 while (TRUE) 946 { 947 const char *suggested_options[9]; /* filled statically below */ 948 const char **next_option = suggested_options; 949 const client_option_t *opt; 950 951 svn_pool_clear(iterpool); 952 953 *next_option++ = "p"; 954 if (diff_allowed) 955 { 956 /* We need one more path for this feature. */ 957 if (my_abspath) 958 *next_option++ = "df"; 959 960 *next_option++ = "e"; 961 962 /* We need one more path for this feature. */ 963 if (my_abspath) 964 *next_option++ = "m"; 965 966 if (knows_something) 967 *next_option++ = "r"; 968 } 969 else 970 { 971 if (knows_something || is_binary) 972 *next_option++ = "r"; 973 974 /* The 'mine-full' option selects the ".mine" file for texts or 975 * the current working directory file for binary files. */ 976 if (my_abspath || is_binary) 977 *next_option++ = "mf"; 978 979 *next_option++ = "tf"; 980 } 981 *next_option++ = "s"; 982 *next_option++ = NULL; 983 984 SVN_ERR(prompt_user(&opt, text_conflict_options, suggested_options, 985 NULL, pb, iterpool)); 986 if (! opt) 987 continue; 988 989 if (strcmp(opt->code, "q") == 0) 990 { 991 option_id = opt->choice; 992 *quit = TRUE; 993 break; 994 } 995 else if (strcmp(opt->code, "s") == 0) 996 { 997 const char *helpstr; 998 999 SVN_ERR(help_string(&helpstr, text_conflict_options, iterpool)); 1000 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", 1001 helpstr)); 1002 } 1003 else if (strcmp(opt->code, "dc") == 0) 1004 { 1005 if (is_binary) 1006 { 1007 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1008 _("Invalid option; cannot " 1009 "display conflicts for a " 1010 "binary file.\n\n"))); 1011 continue; 1012 } 1013 else if (! (my_abspath && base_abspath && their_abspath)) 1014 { 1015 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1016 _("Invalid option; original " 1017 "files not available.\n\n"))); 1018 continue; 1019 } 1020 SVN_ERR(show_conflicts(conflict, 1021 pb->cancel_func, 1022 pb->cancel_baton, 1023 iterpool)); 1024 knows_something = TRUE; 1025 } 1026 else if (strcmp(opt->code, "df") == 0) 1027 { 1028 /* Re-check preconditions. */ 1029 if (! diff_allowed || ! my_abspath) 1030 { 1031 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1032 _("Invalid option; there's no " 1033 "merged version to diff.\n\n"))); 1034 continue; 1035 } 1036 1037 SVN_ERR(show_diff(conflict, merged_abspath, path_prefix, 1038 pb->cancel_func, pb->cancel_baton, 1039 iterpool)); 1040 knows_something = TRUE; 1041 } 1042 else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0) 1043 { 1044 SVN_ERR(open_editor(&performed_edit, merged_abspath, editor_cmd, 1045 config, iterpool)); 1046 if (performed_edit) 1047 knows_something = TRUE; 1048 } 1049 else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 || 1050 strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0) 1051 { 1052 svn_error_t *err; 1053 1054 /* Re-check preconditions. */ 1055 if (! my_abspath) 1056 { 1057 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1058 _("Invalid option; there's no " 1059 "base path to merge.\n\n"))); 1060 continue; 1061 } 1062 1063 err = svn_cl__merge_file_externally(base_abspath, 1064 their_abspath, 1065 my_abspath, 1066 merged_abspath, 1067 local_abspath, config, 1068 NULL, iterpool); 1069 if (err) 1070 { 1071 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) 1072 { 1073 svn_boolean_t remains_in_conflict = TRUE; 1074 1075 /* Try the internal merge tool. */ 1076 svn_error_clear(err); 1077 SVN_ERR(svn_cl__merge_file(&remains_in_conflict, 1078 base_abspath, 1079 their_abspath, 1080 my_abspath, 1081 merged_abspath, 1082 local_abspath, 1083 path_prefix, 1084 editor_cmd, 1085 config, 1086 pb->cancel_func, 1087 pb->cancel_baton, 1088 iterpool)); 1089 knows_something = !remains_in_conflict; 1090 } 1091 else if (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) 1092 { 1093 char buf[1024]; 1094 const char *message; 1095 1096 message = svn_err_best_message(err, buf, sizeof(buf)); 1097 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1098 "%s\n", message)); 1099 svn_error_clear(err); 1100 continue; 1101 } 1102 else 1103 return svn_error_trace(err); 1104 } 1105 else 1106 { 1107 /* The external merge tool's exit code was either 0 or 1. 1108 * The tool may leave the file conflicted by exiting with 1109 * exit code 1, and we allow the user to mark the conflict 1110 * resolved in this case. */ 1111 performed_edit = TRUE; 1112 knows_something = TRUE; 1113 } 1114 } 1115 else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0) 1116 { 1117 /* ### This check should be earlier as it's nasty to offer an option 1118 * and then when the user chooses it say 'Invalid option'. */ 1119 /* ### 'merged_abspath' shouldn't be necessary *before* we launch the 1120 * resolver: it should be the *result* of doing so. */ 1121 if (base_abspath && their_abspath && my_abspath && merged_abspath) 1122 { 1123 svn_error_t *err; 1124 char buf[1024]; 1125 const char *message; 1126 1127 err = svn_cl__merge_file_externally(base_abspath, 1128 their_abspath, 1129 my_abspath, 1130 merged_abspath, 1131 local_abspath, 1132 config, NULL, iterpool); 1133 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL || 1134 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 1135 { 1136 message = svn_err_best_message(err, buf, sizeof(buf)); 1137 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", 1138 message)); 1139 svn_error_clear(err); 1140 } 1141 else if (err) 1142 return svn_error_trace(err); 1143 else 1144 performed_edit = TRUE; 1145 1146 if (performed_edit) 1147 knows_something = TRUE; 1148 } 1149 else 1150 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1151 _("Invalid option.\n\n"))); 1152 } 1153 else if (strcmp(opt->code, "i") == 0) 1154 { 1155 svn_boolean_t remains_in_conflict = TRUE; 1156 1157 SVN_ERR(svn_cl__merge_file(&remains_in_conflict, 1158 base_abspath, 1159 their_abspath, 1160 my_abspath, 1161 merged_abspath, 1162 local_abspath, 1163 path_prefix, 1164 editor_cmd, 1165 config, 1166 pb->cancel_func, 1167 pb->cancel_baton, 1168 iterpool)); 1169 1170 if (!remains_in_conflict) 1171 knows_something = TRUE; 1172 } 1173 else if (opt->choice != svn_client_conflict_option_undefined) 1174 { 1175 if ((opt->choice == svn_client_conflict_option_working_text_where_conflicted 1176 || opt->choice == svn_client_conflict_option_incoming_text_where_conflicted) 1177 && is_binary) 1178 { 1179 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1180 _("Invalid option; cannot choose " 1181 "based on conflicts in a " 1182 "binary file.\n\n"))); 1183 continue; 1184 } 1185 1186 /* We only allow the user accept the merged version of 1187 the file if they've edited it, or at least looked at 1188 the diff. */ 1189 if (opt->choice == svn_client_conflict_option_merged_text 1190 && ! knows_something && diff_allowed) 1191 { 1192 SVN_ERR(svn_cmdline_fprintf( 1193 stderr, iterpool, 1194 _("Invalid option; use diff/edit/merge/launch " 1195 "before choosing 'mark resolved'.\n\n"))); 1196 continue; 1197 } 1198 1199 option_id = opt->choice; 1200 break; 1201 } 1202 } 1203 svn_pool_destroy(iterpool); 1204 1205 if (option_id != svn_client_conflict_option_unspecified && 1206 option_id != svn_client_conflict_option_postpone) 1207 { 1208 SVN_ERR(mark_conflict_resolved(conflict, option_id, 1209 TRUE, NULL, FALSE, 1210 path_prefix, conflict_stats, 1211 ctx, scratch_pool)); 1212 *resolved = TRUE; 1213 } 1214 else 1215 { 1216 *resolved = FALSE; 1217 *postponed = (option_id == svn_client_conflict_option_postpone); 1218 } 1219 1220 return SVN_NO_ERROR; 1221} 1222 1223/* Set *OPTIONS to an array of resolution options for CONFLICT. */ 1224static svn_error_t * 1225build_prop_conflict_options(apr_array_header_t **options, 1226 svn_client_conflict_t *conflict, 1227 svn_client_ctx_t *ctx, 1228 apr_pool_t *result_pool, 1229 apr_pool_t *scratch_pool) 1230{ 1231 const client_option_t *o; 1232 apr_array_header_t *builtin_options; 1233 int nopt; 1234 int i; 1235 apr_pool_t *iterpool; 1236 1237 SVN_ERR(svn_client_conflict_prop_get_resolution_options(&builtin_options, 1238 conflict, ctx, 1239 scratch_pool, 1240 scratch_pool)); 1241 nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options) + 1242 ARRAY_LEN(extra_resolver_options_prop); 1243 *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *)); 1244 1245 iterpool = svn_pool_create(scratch_pool); 1246 for (i = 0; i < builtin_options->nelts; i++) 1247 { 1248 client_option_t *opt; 1249 svn_client_conflict_option_t *builtin_option; 1250 1251 svn_pool_clear(iterpool); 1252 builtin_option = APR_ARRAY_IDX(builtin_options, i, 1253 svn_client_conflict_option_t *); 1254 SVN_ERR(find_option_by_builtin(&opt, conflict, 1255 builtin_resolver_options, 1256 builtin_option, 1257 result_pool, 1258 iterpool)); 1259 if (opt == NULL) 1260 continue; /* ### unknown option -- assign a code dynamically? */ 1261 1262 APR_ARRAY_PUSH(*options, client_option_t *) = opt; 1263 } 1264 1265 svn_pool_destroy(iterpool); 1266 1267 for (o = extra_resolver_options; o->code; o++) 1268 APR_ARRAY_PUSH(*options, const client_option_t *) = o; 1269 for (o = extra_resolver_options_prop; o->code; o++) 1270 APR_ARRAY_PUSH(*options, const client_option_t *) = o; 1271 1272 return SVN_NO_ERROR; 1273} 1274 1275/* Ask the user what to do about the conflicted property PROPNAME described 1276 * by CONFLICT and return the corresponding resolution option in *OPTION. 1277 * SCRATCH_POOL is used for temporary allocations. */ 1278static svn_error_t * 1279handle_one_prop_conflict(svn_client_conflict_option_t **option, 1280 svn_boolean_t *quit, 1281 const char *path_prefix, 1282 svn_cmdline_prompt_baton_t *pb, 1283 const char *editor_cmd, 1284 apr_hash_t *config, 1285 svn_client_conflict_t *conflict, 1286 const char *propname, 1287 svn_client_ctx_t *ctx, 1288 apr_pool_t *result_pool, 1289 apr_pool_t *scratch_pool) 1290{ 1291 apr_pool_t *iterpool; 1292 const char *description; 1293 const svn_string_t *merged_propval = NULL; 1294 svn_boolean_t resolved_allowed = FALSE; 1295 const svn_string_t *base_propval; 1296 const svn_string_t *my_propval; 1297 const svn_string_t *their_propval; 1298 apr_array_header_t *resolution_options; 1299 apr_array_header_t *prop_conflict_options; 1300 1301 SVN_ERR(svn_client_conflict_prop_get_propvals(NULL, &my_propval, 1302 &base_propval, &their_propval, 1303 conflict, propname, 1304 scratch_pool)); 1305 1306 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 1307 _("Conflict for property '%s' discovered" 1308 " on '%s'.\n"), 1309 propname, 1310 svn_cl__local_style_skip_ancestor( 1311 path_prefix, 1312 svn_client_conflict_get_local_abspath(conflict), 1313 scratch_pool))); 1314 SVN_ERR(svn_client_conflict_prop_get_description(&description, conflict, 1315 scratch_pool, scratch_pool)); 1316 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", description)); 1317 1318 SVN_ERR(svn_client_conflict_prop_get_resolution_options(&resolution_options, 1319 conflict, ctx, 1320 result_pool, 1321 scratch_pool)); 1322 SVN_ERR(build_prop_conflict_options(&prop_conflict_options, conflict, ctx, 1323 scratch_pool, scratch_pool)); 1324 iterpool = svn_pool_create(scratch_pool); 1325 while (TRUE) 1326 { 1327 const client_option_t *opt; 1328 const char *suggested_options[9]; /* filled statically below */ 1329 const char **next_option = suggested_options; 1330 1331 *next_option++ = "p"; 1332 *next_option++ = "mf"; 1333 *next_option++ = "tf"; 1334 *next_option++ = "dc"; 1335 *next_option++ = "e"; 1336 if (resolved_allowed) 1337 *next_option++ = "r"; 1338 *next_option++ = "q"; 1339 *next_option++ = "h"; 1340 *next_option++ = NULL; 1341 1342 svn_pool_clear(iterpool); 1343 1344 SVN_ERR(prompt_user(&opt, prop_conflict_options, suggested_options, 1345 NULL, pb, iterpool)); 1346 if (! opt) 1347 continue; 1348 1349 if (strcmp(opt->code, "q") == 0) 1350 { 1351 *option = svn_client_conflict_option_find_by_id(resolution_options, 1352 opt->choice); 1353 *quit = TRUE; 1354 break; 1355 } 1356 else if (strcmp(opt->code, "dc") == 0) 1357 { 1358 SVN_ERR(show_prop_conflict(base_propval, my_propval, their_propval, 1359 merged_propval, 1360 pb->cancel_func, pb->cancel_baton, 1361 scratch_pool)); 1362 } 1363 else if (strcmp(opt->code, "e") == 0) 1364 { 1365 SVN_ERR(edit_prop_conflict(&merged_propval, 1366 base_propval, my_propval, their_propval, 1367 editor_cmd, config, pb, 1368 result_pool, scratch_pool)); 1369 resolved_allowed = (merged_propval != NULL); 1370 } 1371 else if (strcmp(opt->code, "r") == 0) 1372 { 1373 if (! resolved_allowed) 1374 { 1375 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1376 _("Invalid option; please edit the property " 1377 "first.\n\n"))); 1378 continue; 1379 } 1380 1381 *option = svn_client_conflict_option_find_by_id( 1382 resolution_options, 1383 svn_client_conflict_option_merged_text); 1384 svn_client_conflict_option_set_merged_propval(*option, 1385 merged_propval); 1386 break; 1387 } 1388 else if (opt->choice != svn_client_conflict_option_undefined) 1389 { 1390 *option = svn_client_conflict_option_find_by_id(resolution_options, 1391 opt->choice); 1392 break; 1393 } 1394 } 1395 svn_pool_destroy(iterpool); 1396 1397 return SVN_NO_ERROR; 1398} 1399 1400/* Ask the user what to do about the property conflicts described by CONFLICT 1401 * and either resolve them accordingly or postpone resolution. 1402 * SCRATCH_POOL is used for temporary allocations. */ 1403static svn_error_t * 1404handle_prop_conflicts(svn_boolean_t *resolved, 1405 svn_boolean_t *postponed, 1406 svn_boolean_t *quit, 1407 const svn_string_t **merged_value, 1408 const char *path_prefix, 1409 svn_cmdline_prompt_baton_t *pb, 1410 const char *editor_cmd, 1411 apr_hash_t *config, 1412 svn_client_conflict_t *conflict, 1413 svn_cl__conflict_stats_t *conflict_stats, 1414 svn_client_ctx_t *ctx, 1415 apr_pool_t *result_pool, 1416 apr_pool_t *scratch_pool) 1417{ 1418 apr_array_header_t *props_conflicted; 1419 apr_pool_t *iterpool; 1420 int i; 1421 int nresolved = 0; 1422 1423 SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL, 1424 conflict, scratch_pool, 1425 scratch_pool)); 1426 1427 iterpool = svn_pool_create(scratch_pool); 1428 for (i = 0; i < props_conflicted->nelts; i++) 1429 { 1430 const char *propname = APR_ARRAY_IDX(props_conflicted, i, const char *); 1431 svn_client_conflict_option_t *option; 1432 svn_client_conflict_option_id_t option_id; 1433 1434 svn_pool_clear(iterpool); 1435 1436 SVN_ERR(handle_one_prop_conflict(&option, quit, path_prefix, pb, 1437 editor_cmd, config, conflict, propname, 1438 ctx, 1439 iterpool, iterpool)); 1440 option_id = svn_client_conflict_option_get_id(option); 1441 1442 if (option_id != svn_client_conflict_option_unspecified && 1443 option_id != svn_client_conflict_option_postpone) 1444 { 1445 const char *local_relpath = 1446 svn_cl__local_style_skip_ancestor( 1447 path_prefix, svn_client_conflict_get_local_abspath(conflict), 1448 iterpool); 1449 1450 SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option, 1451 ctx, iterpool)); 1452 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, 1453 svn_wc_conflict_kind_property); 1454 nresolved++; 1455 *postponed = FALSE; 1456 } 1457 else 1458 *postponed = (option_id == svn_client_conflict_option_postpone); 1459 1460 if (*quit) 1461 break; 1462 } 1463 svn_pool_destroy(iterpool); 1464 1465 /* Indicate success if no property conflicts remain. */ 1466 *resolved = (nresolved == props_conflicted->nelts); 1467 1468 return SVN_NO_ERROR; 1469} 1470 1471/* Set *OPTIONS to an array of resolution options for CONFLICT. */ 1472static svn_error_t * 1473build_tree_conflict_options( 1474 apr_array_header_t **options, 1475 apr_array_header_t **possible_moved_to_repos_relpaths, 1476 apr_array_header_t **possible_moved_to_abspaths, 1477 svn_boolean_t *all_options_are_dumb, 1478 svn_client_conflict_t *conflict, 1479 svn_client_ctx_t *ctx, 1480 apr_pool_t *result_pool, 1481 apr_pool_t *scratch_pool) 1482{ 1483 const client_option_t *o; 1484 apr_array_header_t *builtin_options; 1485 int nopt; 1486 int i; 1487 int next_unknown_option_code = 1; 1488 apr_pool_t *iterpool; 1489 1490 if (all_options_are_dumb != NULL) 1491 *all_options_are_dumb = TRUE; 1492 1493 SVN_ERR(svn_client_conflict_tree_get_resolution_options(&builtin_options, 1494 conflict, ctx, 1495 scratch_pool, 1496 scratch_pool)); 1497 nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options_tree) + 1498 ARRAY_LEN(extra_resolver_options); 1499 *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *)); 1500 *possible_moved_to_abspaths = NULL; 1501 *possible_moved_to_repos_relpaths = NULL; 1502 1503 iterpool = svn_pool_create(scratch_pool); 1504 for (i = 0; i < builtin_options->nelts; i++) 1505 { 1506 client_option_t *opt; 1507 svn_client_conflict_option_t *builtin_option; 1508 svn_client_conflict_option_id_t id; 1509 1510 svn_pool_clear(iterpool); 1511 builtin_option = APR_ARRAY_IDX(builtin_options, i, 1512 svn_client_conflict_option_t *); 1513 SVN_ERR(find_option_by_builtin(&opt, conflict, 1514 builtin_resolver_options, 1515 builtin_option, 1516 result_pool, 1517 iterpool)); 1518 if (opt == NULL) 1519 { 1520 /* Unkown option. Assign a dynamic option code. */ 1521 opt = apr_pcalloc(result_pool, sizeof(*opt)); 1522 opt->code = apr_psprintf(result_pool, "%d", next_unknown_option_code); 1523 next_unknown_option_code++; 1524 opt->label = svn_client_conflict_option_get_label(builtin_option, 1525 result_pool); 1526 opt->long_desc = svn_client_conflict_option_get_description( 1527 builtin_option, result_pool); 1528 opt->choice = svn_client_conflict_option_get_id(builtin_option); 1529 opt->accept_arg = NULL; 1530 } 1531 1532 APR_ARRAY_PUSH(*options, client_option_t *) = opt; 1533 1534 id = svn_client_conflict_option_get_id(builtin_option); 1535 1536 /* Check if we got a "smart" tree conflict option. */ 1537 if (all_options_are_dumb != NULL && 1538 *all_options_are_dumb && 1539 id != svn_client_conflict_option_postpone && 1540 id != svn_client_conflict_option_accept_current_wc_state) 1541 *all_options_are_dumb = FALSE; 1542 1543 if (*possible_moved_to_repos_relpaths == NULL) 1544 SVN_ERR( 1545 svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( 1546 possible_moved_to_repos_relpaths, builtin_option, 1547 result_pool, iterpool)); 1548 1549 if (*possible_moved_to_abspaths == NULL) 1550 SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2( 1551 possible_moved_to_abspaths, builtin_option, 1552 result_pool, iterpool)); 1553 } 1554 1555 svn_pool_destroy(iterpool); 1556 1557 for (o = extra_resolver_options_tree; o->code; o++) 1558 { 1559 /* Add move target choice options only if there are multiple 1560 * move targets to choose from. */ 1561 if (strcmp(o->code, "d") == 0 && 1562 (*possible_moved_to_repos_relpaths == NULL || 1563 (*possible_moved_to_repos_relpaths)->nelts <= 1)) 1564 continue; 1565 if (strcmp(o->code, "w") == 0 && 1566 (*possible_moved_to_abspaths == NULL || 1567 (*possible_moved_to_abspaths)->nelts <= 1)) 1568 continue; 1569 1570 APR_ARRAY_PUSH(*options, const client_option_t *) = o; 1571 } 1572 for (o = extra_resolver_options; o->code; o++) 1573 APR_ARRAY_PUSH(*options, const client_option_t *) = o; 1574 1575 return SVN_NO_ERROR; 1576} 1577 1578/* Make the user select a move target path for the moved-away VICTIM_ABSPATH. */ 1579static svn_error_t * 1580prompt_move_target_path(int *preferred_move_target_idx, 1581 apr_array_header_t *possible_moved_to_paths, 1582 svn_boolean_t paths_are_local, 1583 svn_cmdline_prompt_baton_t *pb, 1584 const char *victim_abspath, 1585 svn_client_ctx_t *ctx, 1586 apr_pool_t *scratch_pool) 1587{ 1588 const char *move_targets_prompt = ""; 1589 const char *move_targets_list = ""; 1590 const char *wcroot_abspath; 1591 const char *victim_relpath; 1592 int i; 1593 apr_int64_t idx; 1594 apr_pool_t *iterpool; 1595 1596 SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, victim_abspath, 1597 ctx, scratch_pool, scratch_pool)); 1598 victim_relpath = svn_cl__local_style_skip_ancestor(wcroot_abspath, 1599 victim_abspath, 1600 scratch_pool), 1601 iterpool = svn_pool_create(scratch_pool); 1602 1603 /* Build the prompt. */ 1604 for (i = 0; i < possible_moved_to_paths->nelts; i++) 1605 { 1606 svn_pool_clear(iterpool); 1607 1608 if (paths_are_local) 1609 { 1610 const char *moved_to_abspath; 1611 const char *moved_to_relpath; 1612 1613 moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_paths, i, 1614 const char *); 1615 moved_to_relpath = svn_cl__local_style_skip_ancestor( 1616 wcroot_abspath, moved_to_abspath, iterpool), 1617 move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '%s'\n", 1618 move_targets_list, i + 1, 1619 moved_to_relpath); 1620 } 1621 else 1622 { 1623 const char *moved_to_repos_relpath; 1624 1625 moved_to_repos_relpath = APR_ARRAY_IDX(possible_moved_to_paths, i, 1626 const char *); 1627 move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '^/%s'\n", 1628 move_targets_list, i + 1, 1629 moved_to_repos_relpath); 1630 } 1631 } 1632 if (paths_are_local) 1633 move_targets_prompt = 1634 apr_psprintf(scratch_pool, 1635 _("Possible working copy destinations for moved-away '%s' " 1636 "are:\n%s" 1637 "Only one destination can be a move; the others are " 1638 "copies.\n" 1639 "Specify the correct move target path by number: "), 1640 victim_relpath, move_targets_list); 1641 else 1642 move_targets_prompt = 1643 apr_psprintf(scratch_pool, 1644 _("Possible repository destinations for moved-away '%s' " 1645 "are:\n%s" 1646 "Only one destination can be a move; the others are " 1647 "copies.\n" 1648 "Specify the correct move target path by number: "), 1649 victim_relpath, move_targets_list); 1650 1651 /* Keep asking the user until we got a valid choice. */ 1652 while (1) 1653 { 1654 const char *answer; 1655 svn_error_t *err; 1656 1657 svn_pool_clear(iterpool); 1658 1659 SVN_ERR(svn_cmdline_prompt_user2(&answer, move_targets_prompt, 1660 pb, iterpool)); 1661 err = svn_cstring_strtoi64(&idx, answer, 1, 1662 possible_moved_to_paths->nelts, 10); 1663 if (err) 1664 { 1665 char buf[1024]; 1666 1667 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", 1668 svn_err_best_message(err, buf, sizeof(buf)))); 1669 svn_error_clear(err); 1670 continue; 1671 } 1672 1673 break; 1674 } 1675 1676 svn_pool_destroy(iterpool); 1677 1678 SVN_ERR_ASSERT((idx - 1) == (int)(idx - 1)); 1679 *preferred_move_target_idx = (int)(idx - 1); 1680 return SVN_NO_ERROR; 1681} 1682 1683static svn_error_t * 1684find_conflict_option_with_repos_move_targets( 1685 svn_client_conflict_option_t **option_with_move_targets, 1686 apr_array_header_t *options, 1687 apr_pool_t *scratch_pool) 1688{ 1689 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1690 int i; 1691 apr_array_header_t *possible_moved_to_repos_relpaths = NULL; 1692 1693 *option_with_move_targets = NULL; 1694 1695 for (i = 0; i < options->nelts; i++) 1696 { 1697 svn_client_conflict_option_t *option; 1698 1699 svn_pool_clear(iterpool); 1700 option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); 1701 SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( 1702 &possible_moved_to_repos_relpaths, option, iterpool, iterpool)); 1703 if (possible_moved_to_repos_relpaths) 1704 { 1705 *option_with_move_targets = option; 1706 break; 1707 } 1708 } 1709 svn_pool_destroy(iterpool); 1710 1711 return SVN_NO_ERROR; 1712} 1713 1714static svn_error_t * 1715find_conflict_option_with_working_copy_move_targets( 1716 svn_client_conflict_option_t **option_with_move_targets, 1717 apr_array_header_t *options, 1718 apr_pool_t *scratch_pool) 1719{ 1720 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1721 int i; 1722 apr_array_header_t *possible_moved_to_abspaths = NULL; 1723 1724 *option_with_move_targets = NULL; 1725 1726 for (i = 0; i < options->nelts; i++) 1727 { 1728 svn_client_conflict_option_t *option; 1729 1730 svn_pool_clear(iterpool); 1731 option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); 1732 SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2( 1733 &possible_moved_to_abspaths, option, scratch_pool, 1734 iterpool)); 1735 if (possible_moved_to_abspaths) 1736 { 1737 *option_with_move_targets = option; 1738 break; 1739 } 1740 } 1741 svn_pool_destroy(iterpool); 1742 1743 return SVN_NO_ERROR; 1744} 1745 1746/* Ask the user what to do about the tree conflict described by CONFLICT 1747 * and either resolve the conflict accordingly or postpone resolution. 1748 * SCRATCH_POOL is used for temporary allocations. */ 1749static svn_error_t * 1750handle_tree_conflict(svn_boolean_t *resolved, 1751 svn_boolean_t *postponed, 1752 svn_boolean_t *quit, 1753 svn_boolean_t *printed_description, 1754 svn_client_conflict_t *conflict, 1755 const char *path_prefix, 1756 svn_cmdline_prompt_baton_t *pb, 1757 svn_cl__conflict_stats_t *conflict_stats, 1758 svn_client_ctx_t *ctx, 1759 apr_pool_t *scratch_pool) 1760{ 1761 apr_pool_t *iterpool; 1762 apr_array_header_t *tree_conflict_options; 1763 svn_client_conflict_option_id_t option_id; 1764 const char *local_abspath; 1765 const char *conflict_description; 1766 const char *local_change_description; 1767 const char *incoming_change_description; 1768 apr_array_header_t *possible_moved_to_repos_relpaths; 1769 apr_array_header_t *possible_moved_to_abspaths; 1770 svn_boolean_t all_options_are_dumb; 1771 const struct client_option_t *recommended_option; 1772 svn_boolean_t repos_move_target_chosen = FALSE; 1773 svn_boolean_t wc_move_target_chosen = FALSE; 1774 1775 option_id = svn_client_conflict_option_unspecified; 1776 local_abspath = svn_client_conflict_get_local_abspath(conflict); 1777 1778 /* Always show the best possible conflict description and options. */ 1779 SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, scratch_pool)); 1780 1781 SVN_ERR(svn_client_conflict_tree_get_description( 1782 &incoming_change_description, &local_change_description, 1783 conflict, ctx, scratch_pool, scratch_pool)); 1784 conflict_description = apr_psprintf(scratch_pool, "%s\n%s", 1785 incoming_change_description, 1786 local_change_description); 1787 if (!*printed_description) 1788 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 1789 _("Tree conflict on '%s':\n%s\n"), 1790 svn_cl__local_style_skip_ancestor( 1791 path_prefix, local_abspath, scratch_pool), 1792 conflict_description)); 1793 1794 SVN_ERR(build_tree_conflict_options(&tree_conflict_options, 1795 &possible_moved_to_repos_relpaths, 1796 &possible_moved_to_abspaths, 1797 &all_options_are_dumb, 1798 conflict, ctx, 1799 scratch_pool, scratch_pool)); 1800 1801 /* Try a recommended resolution option before prompting. */ 1802 recommended_option = find_recommended_option(tree_conflict_options); 1803 if (recommended_option) 1804 { 1805 svn_error_t *err; 1806 apr_status_t root_cause; 1807 1808 SVN_ERR(svn_cmdline_printf(scratch_pool, 1809 _("Applying recommended resolution '%s':\n"), 1810 recommended_option->label)); 1811 1812 err = mark_conflict_resolved(conflict, recommended_option->choice, 1813 FALSE, NULL, TRUE, 1814 path_prefix, conflict_stats, 1815 ctx, scratch_pool); 1816 if (!err) 1817 { 1818 *resolved = TRUE; 1819 return SVN_NO_ERROR; 1820 } 1821 1822 root_cause = svn_error_root_cause(err)->apr_err; 1823 if (root_cause != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE && 1824 root_cause != SVN_ERR_WC_OBSTRUCTED_UPDATE && 1825 root_cause != SVN_ERR_WC_FOUND_CONFLICT) 1826 return svn_error_trace(err); 1827 1828 /* Fall back to interactive prompting. */ 1829 svn_error_clear(err); 1830 } 1831 1832 if (all_options_are_dumb) 1833 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 1834 _("\nSubversion is not smart enough to resolve " 1835 "this tree conflict automatically!\nSee 'svn " 1836 "help resolve' for more information.\n\n"))); 1837 1838 iterpool = svn_pool_create(scratch_pool); 1839 while (1) 1840 { 1841 const client_option_t *opt; 1842 1843 svn_pool_clear(iterpool); 1844 1845 if (!repos_move_target_chosen && 1846 possible_moved_to_repos_relpaths && 1847 possible_moved_to_repos_relpaths->nelts > 1) 1848 SVN_ERR(svn_cmdline_printf(scratch_pool, 1849 _("Ambiguous move destinations exist in the repository; " 1850 "try the 'd' option\n"))); 1851 if (!wc_move_target_chosen && possible_moved_to_abspaths && 1852 possible_moved_to_abspaths->nelts > 1) 1853 SVN_ERR(svn_cmdline_printf(scratch_pool, 1854 _("Ambiguous move destinations exist in the working copy; " 1855 "try the 'w' option\n"))); 1856 1857 SVN_ERR(prompt_user(&opt, tree_conflict_options, NULL, 1858 conflict_description, pb, iterpool)); 1859 *printed_description = TRUE; 1860 if (! opt) 1861 continue; 1862 1863 if (strcmp(opt->code, "q") == 0) 1864 { 1865 option_id = opt->choice; 1866 *quit = TRUE; 1867 break; 1868 } 1869 else if (strcmp(opt->code, "d") == 0) 1870 { 1871 int preferred_move_target_idx; 1872 apr_array_header_t *options; 1873 svn_client_conflict_option_t *option; 1874 1875 SVN_ERR(prompt_move_target_path(&preferred_move_target_idx, 1876 possible_moved_to_repos_relpaths, 1877 FALSE, 1878 pb, local_abspath, ctx, iterpool)); 1879 1880 /* Update preferred move target path. */ 1881 SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, 1882 conflict, 1883 ctx, 1884 iterpool, 1885 iterpool)); 1886 SVN_ERR(find_conflict_option_with_repos_move_targets( 1887 &option, options, iterpool)); 1888 if (option) 1889 { 1890 SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath2( 1891 option, preferred_move_target_idx, ctx, iterpool)); 1892 repos_move_target_chosen = TRUE; 1893 wc_move_target_chosen = FALSE; 1894 1895 /* Update option description. */ 1896 SVN_ERR(build_tree_conflict_options( 1897 &tree_conflict_options, 1898 &possible_moved_to_repos_relpaths, 1899 &possible_moved_to_abspaths, 1900 NULL, conflict, ctx, 1901 scratch_pool, scratch_pool)); 1902 1903 /* Update conflict description. */ 1904 SVN_ERR(svn_client_conflict_tree_get_description( 1905 &incoming_change_description, &local_change_description, 1906 conflict, ctx, scratch_pool, scratch_pool)); 1907 conflict_description = apr_psprintf(scratch_pool, "%s\n%s", 1908 incoming_change_description, 1909 local_change_description); 1910 } 1911 continue; 1912 } 1913 else if (strcmp(opt->code, "w") == 0) 1914 { 1915 int preferred_move_target_idx; 1916 apr_array_header_t *options; 1917 svn_client_conflict_option_t *option; 1918 1919 SVN_ERR(prompt_move_target_path(&preferred_move_target_idx, 1920 possible_moved_to_abspaths, TRUE, 1921 pb, local_abspath, ctx, iterpool)); 1922 1923 /* Update preferred move target path. */ 1924 SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, 1925 conflict, 1926 ctx, 1927 iterpool, 1928 iterpool)); 1929 SVN_ERR(find_conflict_option_with_working_copy_move_targets( 1930 &option, options, iterpool)); 1931 if (option) 1932 { 1933 SVN_ERR(svn_client_conflict_option_set_moved_to_abspath2( 1934 option, preferred_move_target_idx, ctx, iterpool)); 1935 wc_move_target_chosen = TRUE; 1936 1937 /* Update option description. */ 1938 SVN_ERR(build_tree_conflict_options( 1939 &tree_conflict_options, 1940 &possible_moved_to_repos_relpaths, 1941 &possible_moved_to_abspaths, 1942 NULL, conflict, ctx, 1943 scratch_pool, scratch_pool)); 1944 } 1945 continue; 1946 } 1947 else if (opt->choice != svn_client_conflict_option_undefined) 1948 { 1949 option_id = opt->choice; 1950 break; 1951 } 1952 } 1953 svn_pool_destroy(iterpool); 1954 if (option_id != svn_client_conflict_option_unspecified && 1955 option_id != svn_client_conflict_option_postpone) 1956 { 1957 SVN_ERR(mark_conflict_resolved(conflict, option_id, 1958 FALSE, NULL, TRUE, 1959 path_prefix, conflict_stats, 1960 ctx, scratch_pool)); 1961 *resolved = TRUE; 1962 } 1963 else 1964 { 1965 *resolved = FALSE; 1966 *postponed = (option_id == svn_client_conflict_option_postpone); 1967 } 1968 1969 return SVN_NO_ERROR; 1970} 1971 1972static svn_error_t * 1973resolve_conflict_interactively(svn_boolean_t *resolved, 1974 svn_boolean_t *postponed, 1975 svn_boolean_t *quit, 1976 svn_boolean_t *external_failed, 1977 svn_boolean_t *printed_summary, 1978 svn_boolean_t *printed_description, 1979 svn_client_conflict_t *conflict, 1980 const char *editor_cmd, 1981 apr_hash_t *config, 1982 const char *path_prefix, 1983 svn_cmdline_prompt_baton_t *pb, 1984 svn_cl__conflict_stats_t *conflict_stats, 1985 svn_client_ctx_t *ctx, 1986 apr_pool_t *scratch_pool) 1987{ 1988 svn_boolean_t text_conflicted; 1989 apr_array_header_t *props_conflicted; 1990 svn_boolean_t tree_conflicted; 1991 const svn_string_t *merged_propval; 1992 1993 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, 1994 &props_conflicted, 1995 &tree_conflicted, 1996 conflict, 1997 scratch_pool, 1998 scratch_pool)); 1999 2000 /* Print a summary of conflicts before starting interactive resolution */ 2001 if (! *printed_summary) 2002 { 2003 SVN_ERR(svn_cl__print_conflict_stats(conflict_stats, scratch_pool)); 2004 *printed_summary = TRUE; 2005 } 2006 2007 *resolved = FALSE; 2008 if (text_conflicted 2009 && (svn_client_conflict_get_incoming_change(conflict) == 2010 svn_wc_conflict_action_edit) 2011 && (svn_client_conflict_get_local_change(conflict) == 2012 svn_wc_conflict_reason_edited)) 2013 SVN_ERR(handle_text_conflict(resolved, postponed, quit, printed_description, 2014 conflict, path_prefix, pb, editor_cmd, config, 2015 conflict_stats, ctx, scratch_pool)); 2016 if (props_conflicted->nelts > 0) 2017 SVN_ERR(handle_prop_conflicts(resolved, postponed, quit, &merged_propval, 2018 path_prefix, pb, editor_cmd, config, conflict, 2019 conflict_stats, ctx, scratch_pool, scratch_pool)); 2020 if (tree_conflicted) 2021 SVN_ERR(handle_tree_conflict(resolved, postponed, quit, printed_description, 2022 conflict, path_prefix, pb, conflict_stats, ctx, 2023 scratch_pool)); 2024 2025 return SVN_NO_ERROR; 2026} 2027 2028svn_error_t * 2029svn_cl__resolve_conflict(svn_boolean_t *quit, 2030 svn_boolean_t *external_failed, 2031 svn_boolean_t *printed_summary, 2032 svn_client_conflict_t *conflict, 2033 svn_cl__accept_t accept_which, 2034 const char *editor_cmd, 2035 const char *path_prefix, 2036 svn_cmdline_prompt_baton_t *pb, 2037 svn_cl__conflict_stats_t *conflict_stats, 2038 svn_client_ctx_t *ctx, 2039 apr_pool_t *scratch_pool) 2040{ 2041 svn_boolean_t text_conflicted; 2042 apr_array_header_t *props_conflicted; 2043 svn_boolean_t tree_conflicted; 2044 const char *local_abspath; 2045 svn_client_conflict_option_id_t option_id; 2046 2047 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, 2048 &props_conflicted, 2049 &tree_conflicted, 2050 conflict, 2051 scratch_pool, 2052 scratch_pool)); 2053 local_abspath = svn_client_conflict_get_local_abspath(conflict); 2054 2055 if (accept_which == svn_cl__accept_unspecified) 2056 { 2057 option_id = svn_client_conflict_option_unspecified; 2058 } 2059 else if (accept_which == svn_cl__accept_postpone) 2060 { 2061 option_id = svn_client_conflict_option_postpone; 2062 } 2063 else if (accept_which == svn_cl__accept_base) 2064 { 2065 option_id = svn_client_conflict_option_base_text; 2066 } 2067 else if (accept_which == svn_cl__accept_working) 2068 { 2069 option_id = svn_client_conflict_option_merged_text; 2070 2071 if (text_conflicted) 2072 { 2073 const char *mime_type = 2074 svn_client_conflict_text_get_mime_type(conflict); 2075 2076 /* There is no merged text for binary conflicts, behave as 2077 * if 'mine-full' was chosen. */ 2078 if (mime_type && svn_mime_type_is_binary(mime_type)) 2079 option_id = svn_client_conflict_option_working_text; 2080 } 2081 else if (tree_conflicted) 2082 { 2083 /* For tree conflicts, map 'working' to 'accept current working 2084 * copy state'. */ 2085 option_id = svn_client_conflict_option_accept_current_wc_state; 2086 } 2087 } 2088 else if (accept_which == svn_cl__accept_theirs_conflict) 2089 { 2090 option_id = svn_client_conflict_option_incoming_text_where_conflicted; 2091 } 2092 else if (accept_which == svn_cl__accept_mine_conflict) 2093 { 2094 option_id = svn_client_conflict_option_working_text_where_conflicted; 2095 2096 if (tree_conflicted) 2097 { 2098 svn_wc_operation_t operation; 2099 2100 operation = svn_client_conflict_get_operation(conflict); 2101 if (operation == svn_wc_operation_update || 2102 operation == svn_wc_operation_switch) 2103 { 2104 svn_wc_conflict_reason_t reason; 2105 2106 reason = svn_client_conflict_get_local_change(conflict); 2107 if (reason == svn_wc_conflict_reason_moved_away) 2108 { 2109 /* Map 'mine-conflict' to 'update move destination'. */ 2110 option_id = 2111 svn_client_conflict_option_update_move_destination; 2112 } 2113 else if (reason == svn_wc_conflict_reason_deleted || 2114 reason == svn_wc_conflict_reason_replaced) 2115 { 2116 svn_wc_conflict_action_t action; 2117 svn_node_kind_t node_kind; 2118 2119 action = svn_client_conflict_get_incoming_change(conflict); 2120 node_kind = 2121 svn_client_conflict_tree_get_victim_node_kind(conflict); 2122 2123 if (action == svn_wc_conflict_action_edit && 2124 node_kind == svn_node_dir) 2125 { 2126 /* Map 'mine-conflict' to 'update any moved away 2127 * children'. */ 2128 option_id = 2129 svn_client_conflict_option_update_any_moved_away_children; 2130 } 2131 } 2132 } 2133 } 2134 } 2135 else if (accept_which == svn_cl__accept_theirs_full) 2136 { 2137 option_id = svn_client_conflict_option_incoming_text; 2138 } 2139 else if (accept_which == svn_cl__accept_mine_full) 2140 { 2141 option_id = svn_client_conflict_option_working_text; 2142 } 2143 else if (accept_which == svn_cl__accept_edit) 2144 { 2145 option_id = svn_client_conflict_option_unspecified; 2146 2147 if (local_abspath) 2148 { 2149 if (*external_failed) 2150 { 2151 option_id = svn_client_conflict_option_postpone; 2152 } 2153 else 2154 { 2155 svn_error_t *err; 2156 2157 err = svn_cmdline__edit_file_externally(local_abspath, 2158 editor_cmd, 2159 ctx->config, 2160 scratch_pool); 2161 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR || 2162 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 2163 { 2164 char buf[1024]; 2165 const char *message; 2166 2167 message = svn_err_best_message(err, buf, sizeof(buf)); 2168 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 2169 message)); 2170 svn_error_clear(err); 2171 *external_failed = TRUE; 2172 } 2173 else if (err) 2174 return svn_error_trace(err); 2175 option_id = svn_client_conflict_option_merged_text; 2176 } 2177 } 2178 } 2179 else if (accept_which == svn_cl__accept_launch) 2180 { 2181 const char *base_abspath = NULL; 2182 const char *my_abspath = NULL; 2183 const char *their_abspath = NULL; 2184 2185 option_id = svn_client_conflict_option_unspecified; 2186 2187 if (text_conflicted) 2188 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, 2189 &base_abspath, 2190 &their_abspath, 2191 conflict, scratch_pool, 2192 scratch_pool)); 2193 2194 if (base_abspath && their_abspath && my_abspath && local_abspath) 2195 { 2196 if (*external_failed) 2197 { 2198 option_id = svn_client_conflict_option_postpone; 2199 } 2200 else 2201 { 2202 svn_boolean_t remains_in_conflict; 2203 svn_error_t *err; 2204 2205 err = svn_cl__merge_file_externally(base_abspath, their_abspath, 2206 my_abspath, local_abspath, 2207 local_abspath, ctx->config, 2208 &remains_in_conflict, 2209 scratch_pool); 2210 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL || 2211 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 2212 { 2213 char buf[1024]; 2214 const char *message; 2215 2216 message = svn_err_best_message(err, buf, sizeof(buf)); 2217 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 2218 message)); 2219 *external_failed = TRUE; 2220 return svn_error_trace(err); 2221 } 2222 else if (err) 2223 return svn_error_trace(err); 2224 2225 if (remains_in_conflict) 2226 option_id = svn_client_conflict_option_postpone; 2227 else 2228 option_id = svn_client_conflict_option_merged_text; 2229 } 2230 } 2231 } 2232 else if (accept_which == svn_cl__accept_recommended) 2233 { 2234 svn_client_conflict_option_id_t recommended_id; 2235 2236 if (tree_conflicted) 2237 SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, 2238 scratch_pool)); 2239 recommended_id = svn_client_conflict_get_recommended_option_id(conflict); 2240 if (recommended_id != svn_client_conflict_option_unspecified) 2241 option_id = recommended_id; 2242 else 2243 option_id = svn_client_conflict_option_postpone; 2244 } 2245 else 2246 SVN_ERR_MALFUNCTION(); 2247 2248 /* If we are in interactive mode and either the user gave no --accept 2249 * option or the option did not apply, then prompt. */ 2250 if (option_id == svn_client_conflict_option_unspecified) 2251 { 2252 svn_boolean_t resolved = FALSE; 2253 svn_boolean_t postponed = FALSE; 2254 svn_boolean_t printed_description = FALSE; 2255 svn_error_t *err; 2256 apr_pool_t *iterpool; 2257 2258 *quit = FALSE; 2259 2260 iterpool = svn_pool_create(scratch_pool); 2261 while (!resolved && !postponed && !*quit) 2262 { 2263 svn_pool_clear(iterpool); 2264 err = resolve_conflict_interactively(&resolved, &postponed, quit, 2265 external_failed, 2266 printed_summary, 2267 &printed_description, 2268 conflict, 2269 editor_cmd, ctx->config, 2270 path_prefix, pb, 2271 conflict_stats, ctx, 2272 iterpool); 2273 if (err && err->apr_err == SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE) 2274 { 2275 /* Conflict resolution has failed. Let the user try again. 2276 * It is always possible to break out of this loop with 2277 * the 'quit' or 'postpone' options. */ 2278 svn_handle_warning2(stderr, err, "svn: "); 2279 svn_error_clear(err); 2280 err = SVN_NO_ERROR; 2281 } 2282 SVN_ERR(err); 2283 } 2284 svn_pool_destroy(iterpool); 2285 } 2286 else if (option_id != svn_client_conflict_option_postpone) 2287 SVN_ERR(mark_conflict_resolved(conflict, option_id, 2288 text_conflicted, 2289 props_conflicted->nelts > 0 ? "" : NULL, 2290 tree_conflicted, 2291 path_prefix, conflict_stats, 2292 ctx, scratch_pool)); 2293 2294 return SVN_NO_ERROR; 2295} 2296