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