conflict-callbacks.c revision 299742
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#define MAX_ARRAY_LEN(aryx, aryz) \ 48 (ARRAY_LEN((aryx)) > ARRAY_LEN((aryz)) \ 49 ? ARRAY_LEN((aryx)) : ARRAY_LEN((aryz))) 50 51 52 53struct svn_cl__interactive_conflict_baton_t { 54 svn_cl__accept_t accept_which; 55 apr_hash_t *config; 56 const char *editor_cmd; 57 svn_boolean_t external_failed; 58 svn_cmdline_prompt_baton_t *pb; 59 const char *path_prefix; 60 svn_boolean_t quit; 61 svn_cl__conflict_stats_t *conflict_stats; 62 svn_boolean_t printed_summary; 63}; 64 65svn_error_t * 66svn_cl__get_conflict_func_interactive_baton( 67 svn_cl__interactive_conflict_baton_t **b, 68 svn_cl__accept_t accept_which, 69 apr_hash_t *config, 70 const char *editor_cmd, 71 svn_cl__conflict_stats_t *conflict_stats, 72 svn_cancel_func_t cancel_func, 73 void *cancel_baton, 74 apr_pool_t *result_pool) 75{ 76 svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb)); 77 pb->cancel_func = cancel_func; 78 pb->cancel_baton = cancel_baton; 79 80 *b = apr_palloc(result_pool, sizeof(**b)); 81 (*b)->accept_which = accept_which; 82 (*b)->config = config; 83 (*b)->editor_cmd = editor_cmd; 84 (*b)->external_failed = FALSE; 85 (*b)->pb = pb; 86 SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool)); 87 (*b)->quit = FALSE; 88 (*b)->conflict_stats = conflict_stats; 89 (*b)->printed_summary = FALSE; 90 91 return SVN_NO_ERROR; 92} 93 94svn_cl__accept_t 95svn_cl__accept_from_word(const char *word) 96{ 97 /* Shorthand options are consistent with svn_cl__conflict_handler(). */ 98 if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0 99 || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0) 100 return svn_cl__accept_postpone; 101 if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0) 102 /* ### shorthand? */ 103 return svn_cl__accept_base; 104 if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0) 105 /* ### shorthand? */ 106 return svn_cl__accept_working; 107 if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0 108 || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0) 109 return svn_cl__accept_mine_conflict; 110 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0 111 || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0) 112 return svn_cl__accept_theirs_conflict; 113 if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0 114 || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0) 115 return svn_cl__accept_mine_full; 116 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0 117 || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0) 118 return svn_cl__accept_theirs_full; 119 if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0 120 || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0) 121 return svn_cl__accept_edit; 122 if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0 123 || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0) 124 return svn_cl__accept_launch; 125 /* word is an invalid action. */ 126 return svn_cl__accept_invalid; 127} 128 129 130/* Print on stdout a diff that shows incoming conflicting changes 131 * corresponding to the conflict described in DESC. */ 132static svn_error_t * 133show_diff(const svn_wc_conflict_description2_t *desc, 134 const char *path_prefix, 135 svn_cancel_func_t cancel_func, 136 void *cancel_baton, 137 apr_pool_t *pool) 138{ 139 const char *path1, *path2; 140 const char *label1, *label2; 141 svn_diff_t *diff; 142 svn_stream_t *output; 143 svn_diff_file_options_t *options; 144 145 if (desc->merged_file) 146 { 147 /* For conflicts recorded by the 'merge' operation, show a diff between 148 * 'mine' (the working version of the file as it appeared before the 149 * 'merge' operation was run) and 'merged' (the version of the file 150 * as it appears after the merge operation). 151 * 152 * For conflicts recorded by the 'update' and 'switch' operations, 153 * show a diff beween 'theirs' (the new pristine version of the 154 * file) and 'merged' (the version of the file as it appears with 155 * local changes merged with the new pristine version). 156 * 157 * This way, the diff is always minimal and clearly identifies changes 158 * brought into the working copy by the update/switch/merge operation. */ 159 if (desc->operation == svn_wc_operation_merge) 160 { 161 path1 = desc->my_abspath; 162 label1 = _("MINE"); 163 } 164 else 165 { 166 path1 = desc->their_abspath; 167 label1 = _("THEIRS"); 168 } 169 path2 = desc->merged_file; 170 label2 = _("MERGED"); 171 } 172 else 173 { 174 /* There's no merged file, but we can show the 175 difference between mine and theirs. */ 176 path1 = desc->their_abspath; 177 label1 = _("THEIRS"); 178 path2 = desc->my_abspath; 179 label2 = _("MINE"); 180 } 181 182 label1 = apr_psprintf(pool, "%s\t- %s", 183 svn_cl__local_style_skip_ancestor( 184 path_prefix, path1, pool), label1); 185 label2 = apr_psprintf(pool, "%s\t- %s", 186 svn_cl__local_style_skip_ancestor( 187 path_prefix, path2, pool), label2); 188 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_diff_2(&diff, path1, path2, 193 options, pool)); 194 return svn_diff_file_output_unified4(output, diff, 195 path1, path2, 196 label1, label2, 197 APR_LOCALE_CHARSET, 198 NULL, 199 options->show_c_function, 200 options->context_size, 201 cancel_func, cancel_baton, 202 pool); 203} 204 205 206/* Print on stdout just the conflict hunks of a diff among the 'base', 'their' 207 * and 'my' files of DESC. */ 208static svn_error_t * 209show_conflicts(const svn_wc_conflict_description2_t *desc, 210 svn_cancel_func_t cancel_func, 211 void *cancel_baton, 212 apr_pool_t *pool) 213{ 214 svn_diff_t *diff; 215 svn_stream_t *output; 216 svn_diff_file_options_t *options; 217 218 options = svn_diff_file_options_create(pool); 219 options->ignore_eol_style = TRUE; 220 SVN_ERR(svn_stream_for_stdout(&output, pool)); 221 SVN_ERR(svn_diff_file_diff3_2(&diff, 222 desc->base_abspath, 223 desc->my_abspath, 224 desc->their_abspath, 225 options, pool)); 226 /* ### Consider putting the markers/labels from 227 ### svn_wc__merge_internal in the conflict description. */ 228 return svn_diff_file_output_merge3(output, diff, 229 desc->base_abspath, 230 desc->my_abspath, 231 desc->their_abspath, 232 _("||||||| ORIGINAL"), 233 _("<<<<<<< MINE (select with 'mc')"), 234 _(">>>>>>> THEIRS (select with 'tc')"), 235 "=======", 236 svn_diff_conflict_display_only_conflicts, 237 cancel_func, 238 cancel_baton, 239 pool); 240} 241 242/* Perform a 3-way merge of the conflicting values of a property, 243 * and write the result to the OUTPUT stream. 244 * 245 * If MERGED_ABSPATH is non-NULL, use it as 'my' version instead of 246 * DESC->MY_ABSPATH. 247 * 248 * Assume the values are printable UTF-8 text. 249 */ 250static svn_error_t * 251merge_prop_conflict(svn_stream_t *output, 252 const svn_wc_conflict_description2_t *desc, 253 const char *merged_abspath, 254 svn_cancel_func_t cancel_func, 255 void *cancel_baton, 256 apr_pool_t *pool) 257{ 258 const char *base_abspath = desc->base_abspath; 259 const char *my_abspath = desc->my_abspath; 260 const char *their_abspath = desc->their_abspath; 261 svn_diff_file_options_t *options = svn_diff_file_options_create(pool); 262 svn_diff_t *diff; 263 264 /* If any of the property values is missing, use an empty file instead 265 * for the purpose of showing a diff. */ 266 if (! base_abspath || ! my_abspath || ! their_abspath) 267 { 268 const char *empty_file; 269 270 SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file, 271 NULL, svn_io_file_del_on_pool_cleanup, 272 pool, pool)); 273 if (! base_abspath) 274 base_abspath = empty_file; 275 if (! my_abspath) 276 my_abspath = empty_file; 277 if (! their_abspath) 278 their_abspath = empty_file; 279 } 280 281 options->ignore_eol_style = TRUE; 282 SVN_ERR(svn_diff_file_diff3_2(&diff, 283 base_abspath, 284 merged_abspath ? merged_abspath : my_abspath, 285 their_abspath, 286 options, pool)); 287 SVN_ERR(svn_diff_file_output_merge3(output, diff, 288 base_abspath, 289 merged_abspath ? merged_abspath 290 : my_abspath, 291 their_abspath, 292 _("||||||| ORIGINAL"), 293 _("<<<<<<< MINE"), 294 _(">>>>>>> THEIRS"), 295 "=======", 296 svn_diff_conflict_display_modified_original_latest, 297 cancel_func, 298 cancel_baton, 299 pool)); 300 301 return SVN_NO_ERROR; 302} 303 304/* Display the conflicting values of a property as a 3-way diff. 305 * 306 * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of 307 * DESC->MY_ABSPATH. 308 * 309 * Assume the values are printable UTF-8 text. 310 */ 311static svn_error_t * 312show_prop_conflict(const svn_wc_conflict_description2_t *desc, 313 const char *merged_abspath, 314 svn_cancel_func_t cancel_func, 315 void *cancel_baton, 316 apr_pool_t *pool) 317{ 318 svn_stream_t *output; 319 320 SVN_ERR(svn_stream_for_stdout(&output, pool)); 321 SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, 322 cancel_func, cancel_baton, pool)); 323 324 return SVN_NO_ERROR; 325} 326 327/* Run an external editor, passing it the MERGED_FILE, or, if the 328 * 'merged' file is null, return an error. The tool to use is determined by 329 * B->editor_cmd, B->config and environment variables; see 330 * svn_cl__edit_file_externally() for details. 331 * 332 * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not 333 * configured or cannot run, do not touch *PERFORMED_EDIT, report the error 334 * on stderr, and return SVN_NO_ERROR; if any other error is encountered, 335 * return that error. */ 336static svn_error_t * 337open_editor(svn_boolean_t *performed_edit, 338 const char *merged_file, 339 svn_cl__interactive_conflict_baton_t *b, 340 apr_pool_t *pool) 341{ 342 svn_error_t *err; 343 344 if (merged_file) 345 { 346 err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd, 347 b->config, pool); 348 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR || 349 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 350 { 351 char buf[1024]; 352 const char *message; 353 354 message = svn_err_best_message(err, buf, sizeof(buf)); 355 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", message)); 356 svn_error_clear(err); 357 } 358 else if (err) 359 return svn_error_trace(err); 360 else 361 *performed_edit = TRUE; 362 } 363 else 364 SVN_ERR(svn_cmdline_fprintf(stderr, pool, 365 _("Invalid option; there's no " 366 "merged version to edit.\n\n"))); 367 368 return SVN_NO_ERROR; 369} 370 371/* Run an external editor, passing it the 'merged' property in DESC. 372 * The tool to use is determined by B->editor_cmd, B->config and 373 * environment variables; see svn_cl__edit_file_externally() for details. */ 374static svn_error_t * 375edit_prop_conflict(const char **merged_file_path, 376 const svn_wc_conflict_description2_t *desc, 377 svn_cl__interactive_conflict_baton_t *b, 378 apr_pool_t *result_pool, 379 apr_pool_t *scratch_pool) 380{ 381 apr_file_t *file; 382 const char *file_path; 383 svn_boolean_t performed_edit = FALSE; 384 svn_stream_t *merged_prop; 385 386 SVN_ERR(svn_io_open_unique_file3(&file, &file_path, NULL, 387 svn_io_file_del_on_pool_cleanup, 388 result_pool, scratch_pool)); 389 merged_prop = svn_stream_from_aprfile2(file, TRUE /* disown */, 390 scratch_pool); 391 SVN_ERR(merge_prop_conflict(merged_prop, desc, NULL, 392 b->pb->cancel_func, 393 b->pb->cancel_baton, 394 scratch_pool)); 395 SVN_ERR(svn_stream_close(merged_prop)); 396 SVN_ERR(svn_io_file_flush(file, scratch_pool)); 397 SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool)); 398 *merged_file_path = (performed_edit ? file_path : NULL); 399 400 return SVN_NO_ERROR; 401} 402 403/* Maximum line length for the prompt string. */ 404#define MAX_PROMPT_WIDTH 70 405 406/* Description of a resolver option */ 407typedef struct resolver_option_t 408{ 409 const char *code; /* one or two characters */ 410 const char *short_desc; /* label in prompt (localized) */ 411 const char *long_desc; /* longer description (localized) */ 412 svn_wc_conflict_choice_t choice; 413 /* or ..._undefined if not a simple choice */ 414} resolver_option_t; 415 416/* Resolver options for a text conflict */ 417/* (opt->code == "" causes a blank line break in help_string()) */ 418static const resolver_option_t text_conflict_options[] = 419{ 420 /* Translators: keep long_desc below 70 characters (wrap with a left 421 margin of 9 spaces if needed); don't translate the words within square 422 brackets. */ 423 { "e", N_("edit file"), N_("change merged file in an editor" 424 " [edit]"), 425 svn_wc_conflict_choose_undefined }, 426 { "df", N_("show diff"), N_("show all changes made to merged file"), 427 svn_wc_conflict_choose_undefined }, 428 { "r", N_("mark resolved"), N_("accept merged version of file [working]"), 429 svn_wc_conflict_choose_merged }, 430 { "", "", "", svn_wc_conflict_choose_unspecified }, 431 { "dc", N_("display conflict"), N_("show all conflicts " 432 "(ignoring merged version)"), 433 svn_wc_conflict_choose_undefined }, 434 { "mc", N_("my side of conflict"), N_("accept my version for all conflicts " 435 "(same) [mine-conflict]"), 436 svn_wc_conflict_choose_mine_conflict }, 437 { "tc", N_("their side of conflict"), N_("accept their version for all " 438 "conflicts (same)" 439 " [theirs-conflict]"), 440 svn_wc_conflict_choose_theirs_conflict }, 441 { "", "", "", svn_wc_conflict_choose_unspecified }, 442 { "mf", N_("my version"), N_("accept my version of entire file (even " 443 "non-conflicts) [mine-full]"), 444 svn_wc_conflict_choose_mine_full }, 445 { "tf", N_("their version"), N_("accept their version of entire file " 446 "(same) [theirs-full]"), 447 svn_wc_conflict_choose_theirs_full }, 448 { "", "", "", svn_wc_conflict_choose_unspecified }, 449 { "m", N_("merge"), N_("use merge tool to resolve conflict"), 450 svn_wc_conflict_choose_undefined }, 451 { "l", N_("launch tool"), N_("launch external merge tool to resolve " 452 "conflict [launch]"), 453 svn_wc_conflict_choose_undefined }, 454 { "i", N_("internal merge tool"), N_("use built-in merge tool to " 455 "resolve conflict"), 456 svn_wc_conflict_choose_undefined }, 457 { "p", N_("postpone"), N_("mark the conflict to be resolved later" 458 " [postpone]"), 459 svn_wc_conflict_choose_postpone }, 460 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 461 svn_wc_conflict_choose_postpone }, 462 { "s", N_("show all options"), N_("show this list (also 'h', '?')"), 463 svn_wc_conflict_choose_undefined }, 464 { NULL } 465}; 466 467/* Resolver options for a binary file conflict. */ 468static const resolver_option_t binary_conflict_options[] = 469{ 470 /* Translators: keep long_desc below 70 characters (wrap with a left 471 margin of 9 spaces if needed); don't translate the words within square 472 brackets. */ 473 { "r", N_("mark resolved"), N_("accept the working copy version of file " 474 " [working]"), 475 svn_wc_conflict_choose_merged }, 476 { "tf", N_("their version"), N_("accept the incoming version of file " 477 " [theirs-full]"), 478 svn_wc_conflict_choose_theirs_full }, 479 { "p", N_("postpone"), N_("mark the conflict to be resolved later " 480 " [postpone]"), 481 svn_wc_conflict_choose_postpone }, 482 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 483 svn_wc_conflict_choose_postpone }, 484 { "s", N_("show all options"), N_("show this list (also 'h', '?')"), 485 svn_wc_conflict_choose_undefined }, 486 { NULL } 487}; 488 489/* Resolver options for a property conflict */ 490static const resolver_option_t prop_conflict_options[] = 491{ 492 { "mf", N_("my version"), N_("accept my version of entire property (even " 493 "non-conflicts) [mine-full]"), 494 svn_wc_conflict_choose_mine_full }, 495 { "tf", N_("their version"), N_("accept their version of entire property " 496 "(same) [theirs-full]"), 497 svn_wc_conflict_choose_theirs_full }, 498 { "dc", N_("display conflict"), N_("show conflicts in this property"), 499 svn_wc_conflict_choose_undefined }, 500 { "e", N_("edit property"), N_("change merged property value in an editor" 501 " [edit]"), 502 svn_wc_conflict_choose_undefined }, 503 { "r", N_("mark resolved"), N_("accept edited version of property"), 504 svn_wc_conflict_choose_merged }, 505 { "p", N_("postpone"), N_("mark the conflict to be resolved later" 506 " [postpone]"), 507 svn_wc_conflict_choose_postpone }, 508 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 509 svn_wc_conflict_choose_postpone }, 510 { "h", N_("help"), N_("show this help (also '?')"), 511 svn_wc_conflict_choose_undefined }, 512 { NULL } 513}; 514 515/* Resolver options for a tree conflict */ 516static const resolver_option_t tree_conflict_options[] = 517{ 518 { "r", N_("mark resolved"), N_("accept current working copy state"), 519 svn_wc_conflict_choose_merged }, 520 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 521 svn_wc_conflict_choose_postpone }, 522 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 523 svn_wc_conflict_choose_postpone }, 524 { "h", N_("help"), N_("show this help (also '?')"), 525 svn_wc_conflict_choose_undefined }, 526 { NULL } 527}; 528 529static const resolver_option_t tree_conflict_options_update_moved_away[] = 530{ 531 { "mc", N_("apply update to move destination (recommended)"), 532 N_("apply incoming update to move destination" 533 " [mine-conflict]"), 534 svn_wc_conflict_choose_mine_conflict }, 535 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 536 svn_wc_conflict_choose_postpone }, 537 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 538 svn_wc_conflict_choose_postpone }, 539 { "h", N_("help"), N_("show this help (also '?')"), 540 svn_wc_conflict_choose_undefined }, 541 { NULL } 542}; 543 544static const resolver_option_t tree_conflict_options_update_edit_deleted_dir[] = 545{ 546 { "mc", N_("prepare for updating moved-away children, if any (recommended)"), 547 N_("allow updating moved-away children " 548 "with 'svn resolve' [mine-conflict]"), 549 svn_wc_conflict_choose_mine_conflict }, 550 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 551 svn_wc_conflict_choose_postpone }, 552 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 553 svn_wc_conflict_choose_postpone }, 554 { "h", N_("help"), N_("show this help (also '?')"), 555 svn_wc_conflict_choose_undefined }, 556 { NULL } 557}; 558 559/* Return a pointer to the option description in OPTIONS matching the 560 * one- or two-character OPTION_CODE. Return NULL if not found. */ 561static const resolver_option_t * 562find_option(const resolver_option_t *options, 563 const char *option_code) 564{ 565 const resolver_option_t *opt; 566 567 for (opt = options; opt->code; opt++) 568 { 569 /* Ignore code "" (blank lines) which is not a valid answer. */ 570 if (opt->code[0] && strcmp(opt->code, option_code) == 0) 571 return opt; 572 } 573 return NULL; 574} 575 576/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is 577 * non-null, select only the options whose codes are mentioned in it. */ 578static const char * 579prompt_string(const resolver_option_t *options, 580 const char *const *option_codes, 581 apr_pool_t *pool) 582{ 583 const char *result = _("Select:"); 584 int left_margin = svn_utf_cstring_utf8_width(result); 585 const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, ""); 586 int this_line_len = left_margin; 587 svn_boolean_t first = TRUE; 588 589 while (1) 590 { 591 const resolver_option_t *opt; 592 const char *s; 593 int slen; 594 595 if (option_codes) 596 { 597 if (! *option_codes) 598 break; 599 opt = find_option(options, *option_codes++); 600 } 601 else 602 { 603 opt = options++; 604 if (! opt->code) 605 break; 606 } 607 608 if (! first) 609 result = apr_pstrcat(pool, result, ",", SVN_VA_NULL); 610 s = apr_psprintf(pool, _(" (%s) %s"), 611 opt->code, _(opt->short_desc)); 612 slen = svn_utf_cstring_utf8_width(s); 613 /* Break the line if adding the next option would make it too long */ 614 if (this_line_len + slen > MAX_PROMPT_WIDTH) 615 { 616 result = apr_pstrcat(pool, result, line_sep, SVN_VA_NULL); 617 this_line_len = left_margin; 618 } 619 result = apr_pstrcat(pool, result, s, SVN_VA_NULL); 620 this_line_len += slen; 621 first = FALSE; 622 } 623 return apr_pstrcat(pool, result, ": ", SVN_VA_NULL); 624} 625 626/* Return a help string listing the OPTIONS. */ 627static const char * 628help_string(const resolver_option_t *options, 629 apr_pool_t *pool) 630{ 631 const char *result = ""; 632 const resolver_option_t *opt; 633 634 for (opt = options; opt->code; opt++) 635 { 636 /* Append a line describing OPT, or a blank line if its code is "". */ 637 if (opt->code[0]) 638 { 639 const char *s = apr_psprintf(pool, " (%s)", opt->code); 640 641 result = apr_psprintf(pool, "%s%-6s - %s\n", 642 result, s, _(opt->long_desc)); 643 } 644 else 645 { 646 result = apr_pstrcat(pool, result, "\n", SVN_VA_NULL); 647 } 648 } 649 result = apr_pstrcat(pool, result, 650 _("Words in square brackets are the corresponding " 651 "--accept option arguments.\n"), 652 SVN_VA_NULL); 653 return result; 654} 655 656/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed 657 * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen 658 * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to 659 * NULL if the answer was not one of them. 660 * 661 * If the answer is the (globally recognized) 'help' option, then display 662 * the help (on stderr) and return with *OPT == NULL. 663 */ 664static svn_error_t * 665prompt_user(const resolver_option_t **opt, 666 const resolver_option_t *conflict_options, 667 const char *const *options_to_show, 668 void *prompt_baton, 669 apr_pool_t *scratch_pool) 670{ 671 const char *prompt 672 = prompt_string(conflict_options, options_to_show, scratch_pool); 673 const char *answer; 674 675 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool)); 676 if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0) 677 { 678 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", 679 help_string(conflict_options, 680 scratch_pool))); 681 *opt = NULL; 682 } 683 else 684 { 685 *opt = find_option(conflict_options, answer); 686 if (! *opt) 687 { 688 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 689 _("Unrecognized option.\n\n"))); 690 } 691 } 692 return SVN_NO_ERROR; 693} 694 695/* Ask the user what to do about the text conflict described by DESC. 696 * Return the answer in RESULT. B is the conflict baton for this 697 * conflict resolution session. 698 * SCRATCH_POOL is used for temporary allocations. */ 699static svn_error_t * 700handle_text_conflict(svn_wc_conflict_result_t *result, 701 const svn_wc_conflict_description2_t *desc, 702 svn_cl__interactive_conflict_baton_t *b, 703 apr_pool_t *scratch_pool) 704{ 705 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 706 svn_boolean_t diff_allowed = FALSE; 707 /* Have they done something that might have affected the merged 708 file (so that we need to save a .edited copy by setting the 709 result->save_merge flag)? */ 710 svn_boolean_t performed_edit = FALSE; 711 /* Have they done *something* (edit, look at diff, etc) to 712 give them a rational basis for choosing (r)esolved? */ 713 svn_boolean_t knows_something = FALSE; 714 const char *local_relpath; 715 716 SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text); 717 718 local_relpath = svn_cl__local_style_skip_ancestor(b->path_prefix, 719 desc->local_abspath, 720 scratch_pool);; 721 722 if (desc->is_binary) 723 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 724 _("Conflict discovered in binary file '%s'.\n"), 725 local_relpath)); 726 else 727 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 728 _("Conflict discovered in file '%s'.\n"), 729 local_relpath)); 730 731 /* ### TODO This whole feature availability check is grossly outdated. 732 DIFF_ALLOWED needs either to be redefined or to go away. 733 */ 734 735 /* Diffing can happen between base and merged, to show conflict 736 markers to the user (this is the typical 3-way merge 737 scenario), or if no base is available, we can show a diff 738 between mine and theirs. */ 739 if (!desc->is_binary && 740 ((desc->merged_file && desc->base_abspath) 741 || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))) 742 diff_allowed = TRUE; 743 744 while (TRUE) 745 { 746 const char *options[1 + MAX_ARRAY_LEN(binary_conflict_options, 747 text_conflict_options)]; 748 749 const resolver_option_t *conflict_options = desc->is_binary 750 ? binary_conflict_options 751 : text_conflict_options; 752 const char **next_option = options; 753 const resolver_option_t *opt; 754 755 svn_pool_clear(iterpool); 756 757 *next_option++ = "p"; 758 if (diff_allowed) 759 { 760 /* We need one more path for this feature. */ 761 if (desc->my_abspath) 762 *next_option++ = "df"; 763 764 *next_option++ = "e"; 765 766 /* We need one more path for this feature. */ 767 if (desc->my_abspath) 768 *next_option++ = "m"; 769 770 if (knows_something) 771 *next_option++ = "r"; 772 773 *next_option++ = "mc"; 774 *next_option++ = "tc"; 775 } 776 else 777 { 778 if (knows_something || desc->is_binary) 779 *next_option++ = "r"; 780 781 /* The 'mine-full' option selects the ".mine" file so only offer 782 * it if that file exists. It does not exist for binary files, 783 * for example (questionable historical behaviour since 1.0). */ 784 if (desc->my_abspath) 785 *next_option++ = "mf"; 786 787 *next_option++ = "tf"; 788 } 789 *next_option++ = "s"; 790 *next_option++ = NULL; 791 792 SVN_ERR(prompt_user(&opt, conflict_options, options, b->pb, iterpool)); 793 if (! opt) 794 continue; 795 796 if (strcmp(opt->code, "q") == 0) 797 { 798 result->choice = opt->choice; 799 b->accept_which = svn_cl__accept_postpone; 800 b->quit = TRUE; 801 break; 802 } 803 else if (strcmp(opt->code, "s") == 0) 804 { 805 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", 806 help_string(conflict_options, 807 iterpool))); 808 } 809 else if (strcmp(opt->code, "dc") == 0) 810 { 811 if (desc->is_binary) 812 { 813 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 814 _("Invalid option; cannot " 815 "display conflicts for a " 816 "binary file.\n\n"))); 817 continue; 818 } 819 else if (! (desc->my_abspath && desc->base_abspath && 820 desc->their_abspath)) 821 { 822 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 823 _("Invalid option; original " 824 "files not available.\n\n"))); 825 continue; 826 } 827 SVN_ERR(show_conflicts(desc, 828 b->pb->cancel_func, 829 b->pb->cancel_baton, 830 iterpool)); 831 knows_something = TRUE; 832 } 833 else if (strcmp(opt->code, "df") == 0) 834 { 835 /* Re-check preconditions. */ 836 if (! diff_allowed || ! desc->my_abspath) 837 { 838 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 839 _("Invalid option; there's no " 840 "merged version to diff.\n\n"))); 841 continue; 842 } 843 844 SVN_ERR(show_diff(desc, b->path_prefix, 845 b->pb->cancel_func, b->pb->cancel_baton, 846 iterpool)); 847 knows_something = TRUE; 848 } 849 else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0) 850 { 851 SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool)); 852 if (performed_edit) 853 knows_something = TRUE; 854 } 855 else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 || 856 strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0) 857 { 858 svn_error_t *err; 859 860 /* Re-check preconditions. */ 861 if (! desc->my_abspath) 862 { 863 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 864 _("Invalid option; there's no " 865 "base path to merge.\n\n"))); 866 continue; 867 } 868 869 err = svn_cl__merge_file_externally(desc->base_abspath, 870 desc->their_abspath, 871 desc->my_abspath, 872 desc->merged_file, 873 desc->local_abspath, b->config, 874 NULL, iterpool); 875 if (err) 876 { 877 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) 878 { 879 svn_boolean_t remains_in_conflict = TRUE; 880 881 /* Try the internal merge tool. */ 882 svn_error_clear(err); 883 SVN_ERR(svn_cl__merge_file(&remains_in_conflict, 884 desc->base_abspath, 885 desc->their_abspath, 886 desc->my_abspath, 887 desc->merged_file, 888 desc->local_abspath, 889 b->path_prefix, 890 b->editor_cmd, 891 b->config, 892 b->pb->cancel_func, 893 b->pb->cancel_baton, 894 iterpool)); 895 knows_something = !remains_in_conflict; 896 } 897 else if (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) 898 { 899 char buf[1024]; 900 const char *message; 901 902 message = svn_err_best_message(err, buf, sizeof(buf)); 903 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 904 "%s\n", message)); 905 svn_error_clear(err); 906 continue; 907 } 908 else 909 return svn_error_trace(err); 910 } 911 else 912 { 913 /* The external merge tool's exit code was either 0 or 1. 914 * The tool may leave the file conflicted by exiting with 915 * exit code 1, and we allow the user to mark the conflict 916 * resolved in this case. */ 917 performed_edit = TRUE; 918 knows_something = TRUE; 919 } 920 } 921 else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0) 922 { 923 /* ### This check should be earlier as it's nasty to offer an option 924 * and then when the user chooses it say 'Invalid option'. */ 925 /* ### 'merged_file' shouldn't be necessary *before* we launch the 926 * resolver: it should be the *result* of doing so. */ 927 if (desc->base_abspath && desc->their_abspath && 928 desc->my_abspath && desc->merged_file) 929 { 930 svn_error_t *err; 931 char buf[1024]; 932 const char *message; 933 934 err = svn_cl__merge_file_externally(desc->base_abspath, 935 desc->their_abspath, 936 desc->my_abspath, 937 desc->merged_file, 938 desc->local_abspath, 939 b->config, NULL, iterpool); 940 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL || 941 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 942 { 943 message = svn_err_best_message(err, buf, sizeof(buf)); 944 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", 945 message)); 946 svn_error_clear(err); 947 } 948 else if (err) 949 return svn_error_trace(err); 950 else 951 performed_edit = TRUE; 952 953 if (performed_edit) 954 knows_something = TRUE; 955 } 956 else 957 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 958 _("Invalid option.\n\n"))); 959 } 960 else if (strcmp(opt->code, "i") == 0) 961 { 962 svn_boolean_t remains_in_conflict = TRUE; 963 964 SVN_ERR(svn_cl__merge_file(&remains_in_conflict, 965 desc->base_abspath, 966 desc->their_abspath, 967 desc->my_abspath, 968 desc->merged_file, 969 desc->local_abspath, 970 b->path_prefix, 971 b->editor_cmd, 972 b->config, 973 b->pb->cancel_func, 974 b->pb->cancel_baton, 975 iterpool)); 976 977 if (!remains_in_conflict) 978 knows_something = TRUE; 979 } 980 else if (opt->choice != svn_wc_conflict_choose_undefined) 981 { 982 if ((opt->choice == svn_wc_conflict_choose_mine_conflict 983 || opt->choice == svn_wc_conflict_choose_theirs_conflict) 984 && desc->is_binary) 985 { 986 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 987 _("Invalid option; cannot choose " 988 "based on conflicts in a " 989 "binary file.\n\n"))); 990 continue; 991 } 992 993 /* We only allow the user accept the merged version of 994 the file if they've edited it, or at least looked at 995 the diff. */ 996 if (opt->choice == svn_wc_conflict_choose_merged 997 && ! knows_something && diff_allowed) 998 { 999 SVN_ERR(svn_cmdline_fprintf( 1000 stderr, iterpool, 1001 _("Invalid option; use diff/edit/merge/launch " 1002 "before choosing 'mark resolved'.\n\n"))); 1003 continue; 1004 } 1005 1006 result->choice = opt->choice; 1007 if (performed_edit) 1008 result->save_merged = TRUE; 1009 break; 1010 } 1011 } 1012 svn_pool_destroy(iterpool); 1013 1014 return SVN_NO_ERROR; 1015} 1016 1017/* Ask the user what to do about the property conflict described by DESC. 1018 * Return the answer in RESULT. B is the conflict baton for this 1019 * conflict resolution session. 1020 * SCRATCH_POOL is used for temporary allocations. */ 1021static svn_error_t * 1022handle_prop_conflict(svn_wc_conflict_result_t *result, 1023 const svn_wc_conflict_description2_t *desc, 1024 svn_cl__interactive_conflict_baton_t *b, 1025 apr_pool_t *result_pool, 1026 apr_pool_t *scratch_pool) 1027{ 1028 apr_pool_t *iterpool; 1029 const char *message; 1030 const char *merged_file_path = NULL; 1031 svn_boolean_t resolved_allowed = FALSE; 1032 1033 /* ### Work around a historical bug in the provider: the path to the 1034 * conflict description file was put in the 'theirs' field, and 1035 * 'theirs' was put in the 'merged' field. */ 1036 ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file; 1037 ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL; 1038 1039 SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property); 1040 1041 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 1042 _("Conflict for property '%s' discovered" 1043 " on '%s'.\n"), 1044 desc->property_name, 1045 svn_cl__local_style_skip_ancestor( 1046 b->path_prefix, desc->local_abspath, 1047 scratch_pool))); 1048 1049 SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc, 1050 scratch_pool)); 1051 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message)); 1052 1053 iterpool = svn_pool_create(scratch_pool); 1054 while (TRUE) 1055 { 1056 const resolver_option_t *opt; 1057 const char *options[ARRAY_LEN(prop_conflict_options)]; 1058 const char **next_option = options; 1059 1060 *next_option++ = "p"; 1061 *next_option++ = "mf"; 1062 *next_option++ = "tf"; 1063 *next_option++ = "dc"; 1064 *next_option++ = "e"; 1065 if (resolved_allowed) 1066 *next_option++ = "r"; 1067 *next_option++ = "q"; 1068 *next_option++ = "h"; 1069 *next_option++ = NULL; 1070 1071 svn_pool_clear(iterpool); 1072 1073 SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb, 1074 iterpool)); 1075 if (! opt) 1076 continue; 1077 1078 if (strcmp(opt->code, "q") == 0) 1079 { 1080 result->choice = opt->choice; 1081 b->accept_which = svn_cl__accept_postpone; 1082 b->quit = TRUE; 1083 break; 1084 } 1085 else if (strcmp(opt->code, "dc") == 0) 1086 { 1087 SVN_ERR(show_prop_conflict(desc, merged_file_path, 1088 b->pb->cancel_func, b->pb->cancel_baton, 1089 scratch_pool)); 1090 } 1091 else if (strcmp(opt->code, "e") == 0) 1092 { 1093 SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b, 1094 result_pool, scratch_pool)); 1095 resolved_allowed = (merged_file_path != NULL); 1096 } 1097 else if (strcmp(opt->code, "r") == 0) 1098 { 1099 if (! resolved_allowed) 1100 { 1101 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1102 _("Invalid option; please edit the property " 1103 "first.\n\n"))); 1104 continue; 1105 } 1106 1107 result->merged_file = merged_file_path; 1108 result->choice = svn_wc_conflict_choose_merged; 1109 break; 1110 } 1111 else if (opt->choice != svn_wc_conflict_choose_undefined) 1112 { 1113 result->choice = opt->choice; 1114 break; 1115 } 1116 } 1117 svn_pool_destroy(iterpool); 1118 1119 return SVN_NO_ERROR; 1120} 1121 1122/* Ask the user what to do about the tree conflict described by DESC. 1123 * Return the answer in RESULT. B is the conflict baton for this 1124 * conflict resolution session. 1125 * SCRATCH_POOL is used for temporary allocations. */ 1126static svn_error_t * 1127handle_tree_conflict(svn_wc_conflict_result_t *result, 1128 const svn_wc_conflict_description2_t *desc, 1129 svn_cl__interactive_conflict_baton_t *b, 1130 apr_pool_t *scratch_pool) 1131{ 1132 const char *readable_desc; 1133 apr_pool_t *iterpool; 1134 1135 SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( 1136 &readable_desc, desc, scratch_pool)); 1137 SVN_ERR(svn_cmdline_fprintf( 1138 stderr, scratch_pool, 1139 _("Tree conflict on '%s'\n > %s\n"), 1140 svn_cl__local_style_skip_ancestor(b->path_prefix, 1141 desc->local_abspath, 1142 scratch_pool), 1143 readable_desc)); 1144 1145 iterpool = svn_pool_create(scratch_pool); 1146 while (1) 1147 { 1148 const resolver_option_t *opt; 1149 const resolver_option_t *tc_opts; 1150 1151 svn_pool_clear(iterpool); 1152 1153 tc_opts = tree_conflict_options; 1154 1155 if (desc->operation == svn_wc_operation_update || 1156 desc->operation == svn_wc_operation_switch) 1157 { 1158 if (desc->reason == svn_wc_conflict_reason_moved_away) 1159 { 1160 tc_opts = tree_conflict_options_update_moved_away; 1161 } 1162 else if (desc->reason == svn_wc_conflict_reason_deleted || 1163 desc->reason == svn_wc_conflict_reason_replaced) 1164 { 1165 if (desc->action == svn_wc_conflict_action_edit && 1166 desc->node_kind == svn_node_dir) 1167 tc_opts = tree_conflict_options_update_edit_deleted_dir; 1168 } 1169 } 1170 1171 SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool)); 1172 if (! opt) 1173 continue; 1174 1175 if (strcmp(opt->code, "q") == 0) 1176 { 1177 result->choice = opt->choice; 1178 b->accept_which = svn_cl__accept_postpone; 1179 b->quit = TRUE; 1180 break; 1181 } 1182 else if (opt->choice != svn_wc_conflict_choose_undefined) 1183 { 1184 result->choice = opt->choice; 1185 break; 1186 } 1187 } 1188 svn_pool_destroy(iterpool); 1189 1190 return SVN_NO_ERROR; 1191} 1192 1193/* The body of svn_cl__conflict_func_interactive(). */ 1194static svn_error_t * 1195conflict_func_interactive(svn_wc_conflict_result_t **result, 1196 const svn_wc_conflict_description2_t *desc, 1197 void *baton, 1198 apr_pool_t *result_pool, 1199 apr_pool_t *scratch_pool) 1200{ 1201 svn_cl__interactive_conflict_baton_t *b = baton; 1202 svn_error_t *err; 1203 1204 /* Start out assuming we're going to postpone the conflict. */ 1205 *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, 1206 NULL, result_pool); 1207 1208 switch (b->accept_which) 1209 { 1210 case svn_cl__accept_invalid: 1211 case svn_cl__accept_unspecified: 1212 /* No (or no valid) --accept option, fall through to prompting. */ 1213 break; 1214 case svn_cl__accept_postpone: 1215 (*result)->choice = svn_wc_conflict_choose_postpone; 1216 return SVN_NO_ERROR; 1217 case svn_cl__accept_base: 1218 (*result)->choice = svn_wc_conflict_choose_base; 1219 return SVN_NO_ERROR; 1220 case svn_cl__accept_working: 1221 /* If the caller didn't merge the property values, then I guess 1222 * 'choose working' means 'choose mine'... */ 1223 if (! desc->merged_file) 1224 (*result)->merged_file = desc->my_abspath; 1225 (*result)->choice = svn_wc_conflict_choose_merged; 1226 return SVN_NO_ERROR; 1227 case svn_cl__accept_mine_conflict: 1228 (*result)->choice = svn_wc_conflict_choose_mine_conflict; 1229 return SVN_NO_ERROR; 1230 case svn_cl__accept_theirs_conflict: 1231 (*result)->choice = svn_wc_conflict_choose_theirs_conflict; 1232 return SVN_NO_ERROR; 1233 case svn_cl__accept_mine_full: 1234 (*result)->choice = svn_wc_conflict_choose_mine_full; 1235 return SVN_NO_ERROR; 1236 case svn_cl__accept_theirs_full: 1237 (*result)->choice = svn_wc_conflict_choose_theirs_full; 1238 return SVN_NO_ERROR; 1239 case svn_cl__accept_edit: 1240 if (desc->merged_file) 1241 { 1242 if (b->external_failed) 1243 { 1244 (*result)->choice = svn_wc_conflict_choose_postpone; 1245 return SVN_NO_ERROR; 1246 } 1247 1248 err = svn_cmdline__edit_file_externally(desc->merged_file, 1249 b->editor_cmd, b->config, 1250 scratch_pool); 1251 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR || 1252 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 1253 { 1254 char buf[1024]; 1255 const char *message; 1256 1257 message = svn_err_best_message(err, buf, sizeof(buf)); 1258 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1259 message)); 1260 svn_error_clear(err); 1261 b->external_failed = TRUE; 1262 } 1263 else if (err) 1264 return svn_error_trace(err); 1265 (*result)->choice = svn_wc_conflict_choose_merged; 1266 return SVN_NO_ERROR; 1267 } 1268 /* else, fall through to prompting. */ 1269 break; 1270 case svn_cl__accept_launch: 1271 if (desc->base_abspath && desc->their_abspath 1272 && desc->my_abspath && desc->merged_file) 1273 { 1274 svn_boolean_t remains_in_conflict; 1275 1276 if (b->external_failed) 1277 { 1278 (*result)->choice = svn_wc_conflict_choose_postpone; 1279 return SVN_NO_ERROR; 1280 } 1281 1282 err = svn_cl__merge_file_externally(desc->base_abspath, 1283 desc->their_abspath, 1284 desc->my_abspath, 1285 desc->merged_file, 1286 desc->local_abspath, 1287 b->config, 1288 &remains_in_conflict, 1289 scratch_pool); 1290 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL || 1291 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 1292 { 1293 char buf[1024]; 1294 const char *message; 1295 1296 message = svn_err_best_message(err, buf, sizeof(buf)); 1297 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1298 message)); 1299 b->external_failed = TRUE; 1300 return svn_error_trace(err); 1301 } 1302 else if (err) 1303 return svn_error_trace(err); 1304 1305 if (remains_in_conflict) 1306 (*result)->choice = svn_wc_conflict_choose_postpone; 1307 else 1308 (*result)->choice = svn_wc_conflict_choose_merged; 1309 return SVN_NO_ERROR; 1310 } 1311 /* else, fall through to prompting. */ 1312 break; 1313 } 1314 1315 /* Print a summary of conflicts before starting interactive resolution */ 1316 if (! b->printed_summary) 1317 { 1318 SVN_ERR(svn_cl__print_conflict_stats(b->conflict_stats, scratch_pool)); 1319 b->printed_summary = TRUE; 1320 } 1321 1322 /* We're in interactive mode and either the user gave no --accept 1323 option or the option did not apply; let's prompt. */ 1324 1325 /* Handle the most common cases, which is either: 1326 1327 Conflicting edits on a file's text, or 1328 Conflicting edits on a property. 1329 */ 1330 if (((desc->kind == svn_wc_conflict_kind_text) 1331 && (desc->action == svn_wc_conflict_action_edit) 1332 && (desc->reason == svn_wc_conflict_reason_edited))) 1333 SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool)); 1334 else if (desc->kind == svn_wc_conflict_kind_property) 1335 SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool)); 1336 else if (desc->kind == svn_wc_conflict_kind_tree) 1337 SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool)); 1338 1339 else /* other types of conflicts -- do nothing about them. */ 1340 { 1341 (*result)->choice = svn_wc_conflict_choose_postpone; 1342 } 1343 1344 return SVN_NO_ERROR; 1345} 1346 1347svn_error_t * 1348svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result, 1349 const svn_wc_conflict_description2_t *desc, 1350 void *baton, 1351 apr_pool_t *result_pool, 1352 apr_pool_t *scratch_pool) 1353{ 1354 svn_cl__interactive_conflict_baton_t *b = baton; 1355 1356 SVN_ERR(conflict_func_interactive(result, desc, baton, 1357 result_pool, scratch_pool)); 1358 1359 /* If we are resolving a conflict, adjust the summary of conflicts. */ 1360 if ((*result)->choice != svn_wc_conflict_choose_postpone) 1361 { 1362 const char *local_path 1363 = svn_cl__local_style_skip_ancestor( 1364 b->path_prefix, desc->local_abspath, scratch_pool); 1365 1366 svn_cl__conflict_stats_resolved(b->conflict_stats, local_path, 1367 desc->kind); 1368 } 1369 return SVN_NO_ERROR; 1370} 1371