conflict-callbacks.c revision 289166
1226633Sdim/* 2193326Sed * conflict-callbacks.c: conflict resolution callbacks specific to the 3193326Sed * commandline client. 4193326Sed * 5193326Sed * ==================================================================== 6193326Sed * Licensed to the Apache Software Foundation (ASF) under one 7193326Sed * or more contributor license agreements. See the NOTICE file 8193326Sed * distributed with this work for additional information 9193326Sed * regarding copyright ownership. The ASF licenses this file 10193326Sed * to you under the Apache License, Version 2.0 (the 11193326Sed * "License"); you may not use this file except in compliance 12193326Sed * with the License. You may obtain a copy of the License at 13193326Sed * 14193326Sed * http://www.apache.org/licenses/LICENSE-2.0 15193326Sed * 16193326Sed * Unless required by applicable law or agreed to in writing, 17249423Sdim * software distributed under the License is distributed on an 18226633Sdim * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19226633Sdim * KIND, either express or implied. See the License for the 20226633Sdim * specific language governing permissions and limitations 21193326Sed * under the License. 22193326Sed * ==================================================================== 23193326Sed */ 24226633Sdim 25226633Sdim#include <apr_xlate.h> /* for APR_LOCALE_CHARSET */ 26193326Sed 27193326Sed#define APR_WANT_STRFUNC 28193326Sed#include <apr_want.h> 29226633Sdim 30226633Sdim#include "svn_hash.h" 31226633Sdim#include "svn_cmdline.h" 32226633Sdim#include "svn_client.h" 33226633Sdim#include "svn_dirent_uri.h" 34198092Srdivacky#include "svn_types.h" 35226633Sdim#include "svn_pools.h" 36226633Sdim#include "svn_sorts.h" 37226633Sdim#include "svn_utf.h" 38226633Sdim 39193326Sed#include "cl.h" 40226633Sdim#include "cl-conflicts.h" 41226633Sdim 42193326Sed#include "private/svn_cmdline_private.h" 43226633Sdim 44226633Sdim#include "svn_private_config.h" 45226633Sdim 46193326Sed#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0]))) 47226633Sdim 48226633Sdim 49226633Sdim 50226633Sdimstruct svn_cl__interactive_conflict_baton_t { 51226633Sdim svn_cl__accept_t accept_which; 52226633Sdim apr_hash_t *config; 53193326Sed const char *editor_cmd; 54226633Sdim svn_boolean_t external_failed; 55234353Sdim svn_cmdline_prompt_baton_t *pb; 56234353Sdim const char *path_prefix; 57234353Sdim svn_boolean_t quit; 58226633Sdim svn_cl__conflict_stats_t *conflict_stats; 59226633Sdim}; 60226633Sdim 61193326Sedsvn_error_t * 62226633Sdimsvn_cl__get_conflict_func_interactive_baton( 63226633Sdim svn_cl__interactive_conflict_baton_t **b, 64226633Sdim svn_cl__accept_t accept_which, 65226633Sdim apr_hash_t *config, 66226633Sdim const char *editor_cmd, 67226633Sdim svn_cl__conflict_stats_t *conflict_stats, 68226633Sdim svn_cancel_func_t cancel_func, 69226633Sdim void *cancel_baton, 70198092Srdivacky apr_pool_t *result_pool) 71193326Sed{ 72226633Sdim svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb)); 73226633Sdim pb->cancel_func = cancel_func; 74226633Sdim pb->cancel_baton = cancel_baton; 75234353Sdim 76226633Sdim *b = apr_palloc(result_pool, sizeof(**b)); 77226633Sdim (*b)->accept_which = accept_which; 78226633Sdim (*b)->config = config; 79193326Sed (*b)->editor_cmd = editor_cmd; 80226633Sdim (*b)->external_failed = FALSE; 81226633Sdim (*b)->pb = pb; 82226633Sdim SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool)); 83193326Sed (*b)->quit = FALSE; 84193326Sed (*b)->conflict_stats = conflict_stats; 85193326Sed 86226633Sdim return SVN_NO_ERROR; 87226633Sdim} 88226633Sdim 89193326Sedsvn_cl__accept_t 90226633Sdimsvn_cl__accept_from_word(const char *word) 91226633Sdim{ 92226633Sdim /* Shorthand options are consistent with svn_cl__conflict_handler(). */ 93226633Sdim if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0 94226633Sdim || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0) 95198092Srdivacky return svn_cl__accept_postpone; 96226633Sdim if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0) 97226633Sdim /* ### shorthand? */ 98234353Sdim return svn_cl__accept_base; 99226633Sdim if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0) 100226633Sdim /* ### shorthand? */ 101226633Sdim return svn_cl__accept_working; 102226633Sdim if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0 103226633Sdim || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0) 104226633Sdim return svn_cl__accept_mine_conflict; 105226633Sdim if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0 106226633Sdim || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0) 107193326Sed return svn_cl__accept_theirs_conflict; 108226633Sdim if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0 109226633Sdim || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0) 110226633Sdim return svn_cl__accept_mine_full; 111234353Sdim if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0 112226633Sdim || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0) 113226633Sdim return svn_cl__accept_theirs_full; 114226633Sdim if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0 115226633Sdim || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0) 116226633Sdim return svn_cl__accept_edit; 117226633Sdim if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0 118193326Sed || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0) 119193326Sed return svn_cl__accept_launch; 120193326Sed /* 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 a tree conflict */ 513static const resolver_option_t tree_conflict_options[] = 514{ 515 { "r", N_("mark resolved"), N_("accept current working copy state"), 516 svn_wc_conflict_choose_merged }, 517 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 518 svn_wc_conflict_choose_postpone }, 519 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 520 svn_wc_conflict_choose_postpone }, 521 { "h", N_("help"), N_("show this help (also '?')"), -1 }, 522 { NULL } 523}; 524 525static const resolver_option_t tree_conflict_options_update_moved_away[] = 526{ 527 { "mc", N_("apply update (recommended)"), 528 N_("apply update to the move destination" 529 " [mine-conflict]"), 530 svn_wc_conflict_choose_mine_conflict }, 531 { "r", N_("discard update (breaks move)"), N_("discard update, mark " 532 "resolved, the move will " 533 "will become a copy"), 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_edit_moved_away[] = 544{ 545 { "mc", N_("apply update to move destination"), 546 N_("apply incoming update to move destination" 547 " [mine-conflict]"), 548 svn_wc_conflict_choose_mine_conflict }, 549 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 550 svn_wc_conflict_choose_postpone }, 551 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 552 svn_wc_conflict_choose_postpone }, 553 { "h", N_("help"), N_("show this help (also '?')"), -1 }, 554 { NULL } 555}; 556 557static const resolver_option_t tree_conflict_options_update_deleted[] = 558{ 559 { "mc", N_("keep affected local moves"), N_("keep any local moves affected " 560 "by this deletion [mine-conflict]"), 561 svn_wc_conflict_choose_mine_conflict }, 562 { "r", N_("mark resolved (breaks moves)"), N_("mark resolved, any affected " 563 "moves will become copies"), 564 svn_wc_conflict_choose_merged }, 565 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 566 svn_wc_conflict_choose_postpone }, 567 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 568 svn_wc_conflict_choose_postpone }, 569 { "h", N_("help"), N_("show this help (also '?')"), -1 }, 570 { NULL } 571}; 572 573static const resolver_option_t tree_conflict_options_update_replaced[] = 574{ 575 { "mc", N_("keep affected local moves"), N_("keep any moves affected by this " 576 "replacement [mine-conflict]"), 577 svn_wc_conflict_choose_mine_conflict }, 578 { "r", N_("mark resolved (breaks moves)"), N_("mark resolved (any affected " 579 "moves will become copies)"), 580 svn_wc_conflict_choose_merged }, 581 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 582 svn_wc_conflict_choose_postpone }, 583 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 584 svn_wc_conflict_choose_postpone }, 585 { "h", N_("help"), N_("show this help (also '?')"), -1 }, 586 { NULL } 587}; 588 589 590/* Return a pointer to the option description in OPTIONS matching the 591 * one- or two-character OPTION_CODE. Return NULL if not found. */ 592static const resolver_option_t * 593find_option(const resolver_option_t *options, 594 const char *option_code) 595{ 596 const resolver_option_t *opt; 597 598 for (opt = options; opt->code; opt++) 599 { 600 /* Ignore code "" (blank lines) which is not a valid answer. */ 601 if (opt->code[0] && strcmp(opt->code, option_code) == 0) 602 return opt; 603 } 604 return NULL; 605} 606 607/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is 608 * non-null, select only the options whose codes are mentioned in it. */ 609static const char * 610prompt_string(const resolver_option_t *options, 611 const char *const *option_codes, 612 apr_pool_t *pool) 613{ 614 const char *result = _("Select:"); 615 int left_margin = svn_utf_cstring_utf8_width(result); 616 const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, ""); 617 int this_line_len = left_margin; 618 svn_boolean_t first = TRUE; 619 620 while (1) 621 { 622 const resolver_option_t *opt; 623 const char *s; 624 int slen; 625 626 if (option_codes) 627 { 628 if (! *option_codes) 629 break; 630 opt = find_option(options, *option_codes++); 631 } 632 else 633 { 634 opt = options++; 635 if (! opt->code) 636 break; 637 } 638 639 if (! first) 640 result = apr_pstrcat(pool, result, ",", (char *)NULL); 641 s = apr_psprintf(pool, _(" (%s) %s"), 642 opt->code, _(opt->short_desc)); 643 slen = svn_utf_cstring_utf8_width(s); 644 /* Break the line if adding the next option would make it too long */ 645 if (this_line_len + slen > MAX_PROMPT_WIDTH) 646 { 647 result = apr_pstrcat(pool, result, line_sep, (char *)NULL); 648 this_line_len = left_margin; 649 } 650 result = apr_pstrcat(pool, result, s, (char *)NULL); 651 this_line_len += slen; 652 first = FALSE; 653 } 654 return apr_pstrcat(pool, result, ": ", (char *)NULL); 655} 656 657/* Return a help string listing the OPTIONS. */ 658static const char * 659help_string(const resolver_option_t *options, 660 apr_pool_t *pool) 661{ 662 const char *result = ""; 663 const resolver_option_t *opt; 664 665 for (opt = options; opt->code; opt++) 666 { 667 /* Append a line describing OPT, or a blank line if its code is "". */ 668 if (opt->code[0]) 669 { 670 const char *s = apr_psprintf(pool, " (%s)", opt->code); 671 672 result = apr_psprintf(pool, "%s%-6s - %s\n", 673 result, s, _(opt->long_desc)); 674 } 675 else 676 { 677 result = apr_pstrcat(pool, result, "\n", (char *)NULL); 678 } 679 } 680 result = apr_pstrcat(pool, result, 681 _("Words in square brackets are the corresponding " 682 "--accept option arguments.\n"), 683 (char *)NULL); 684 return result; 685} 686 687/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed 688 * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen 689 * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to 690 * NULL if the answer was not one of them. 691 * 692 * If the answer is the (globally recognized) 'help' option, then display 693 * the help (on stderr) and return with *OPT == NULL. 694 */ 695static svn_error_t * 696prompt_user(const resolver_option_t **opt, 697 const resolver_option_t *conflict_options, 698 const char *const *options_to_show, 699 void *prompt_baton, 700 apr_pool_t *scratch_pool) 701{ 702 const char *prompt 703 = prompt_string(conflict_options, options_to_show, scratch_pool); 704 const char *answer; 705 706 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool)); 707 if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0) 708 { 709 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", 710 help_string(conflict_options, 711 scratch_pool))); 712 *opt = NULL; 713 } 714 else 715 { 716 *opt = find_option(conflict_options, answer); 717 if (! *opt) 718 { 719 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 720 _("Unrecognized option.\n\n"))); 721 } 722 } 723 return SVN_NO_ERROR; 724} 725 726/* Ask the user what to do about the text conflict described by DESC. 727 * Return the answer in RESULT. B is the conflict baton for this 728 * conflict resolution session. 729 * SCRATCH_POOL is used for temporary allocations. */ 730static svn_error_t * 731handle_text_conflict(svn_wc_conflict_result_t *result, 732 const svn_wc_conflict_description2_t *desc, 733 svn_cl__interactive_conflict_baton_t *b, 734 apr_pool_t *scratch_pool) 735{ 736 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 737 svn_boolean_t diff_allowed = FALSE; 738 /* Have they done something that might have affected the merged 739 file (so that we need to save a .edited copy)? */ 740 svn_boolean_t performed_edit = FALSE; 741 /* Have they done *something* (edit, look at diff, etc) to 742 give them a rational basis for choosing (r)esolved? */ 743 svn_boolean_t knows_something = FALSE; 744 745 SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text); 746 747 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 748 _("Conflict discovered in file '%s'.\n"), 749 svn_cl__local_style_skip_ancestor( 750 b->path_prefix, desc->local_abspath, 751 scratch_pool))); 752 753 /* Diffing can happen between base and merged, to show conflict 754 markers to the user (this is the typical 3-way merge 755 scenario), or if no base is available, we can show a diff 756 between mine and theirs. */ 757 if ((desc->merged_file && desc->base_abspath) 758 || (!desc->base_abspath && desc->my_abspath && desc->their_abspath)) 759 diff_allowed = TRUE; 760 761 while (TRUE) 762 { 763 const char *options[ARRAY_LEN(text_conflict_options)]; 764 const char **next_option = options; 765 const resolver_option_t *opt; 766 767 svn_pool_clear(iterpool); 768 769 *next_option++ = "p"; 770 if (diff_allowed) 771 { 772 *next_option++ = "df"; 773 *next_option++ = "e"; 774 *next_option++ = "m"; 775 776 if (knows_something) 777 *next_option++ = "r"; 778 779 if (! desc->is_binary) 780 { 781 *next_option++ = "mc"; 782 *next_option++ = "tc"; 783 } 784 } 785 else 786 { 787 if (knows_something) 788 *next_option++ = "r"; 789 *next_option++ = "mf"; 790 *next_option++ = "tf"; 791 } 792 *next_option++ = "s"; 793 *next_option++ = NULL; 794 795 SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->pb, 796 iterpool)); 797 if (! opt) 798 continue; 799 800 if (strcmp(opt->code, "q") == 0) 801 { 802 result->choice = opt->choice; 803 b->accept_which = svn_cl__accept_postpone; 804 b->quit = TRUE; 805 break; 806 } 807 else if (strcmp(opt->code, "s") == 0) 808 { 809 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", 810 help_string(text_conflict_options, 811 iterpool))); 812 } 813 else if (strcmp(opt->code, "dc") == 0) 814 { 815 if (desc->is_binary) 816 { 817 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 818 _("Invalid option; cannot " 819 "display conflicts for a " 820 "binary file.\n\n"))); 821 continue; 822 } 823 else if (! (desc->my_abspath && desc->base_abspath && 824 desc->their_abspath)) 825 { 826 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 827 _("Invalid option; original " 828 "files not available.\n\n"))); 829 continue; 830 } 831 SVN_ERR(show_conflicts(desc, iterpool)); 832 knows_something = TRUE; 833 } 834 else if (strcmp(opt->code, "df") == 0) 835 { 836 if (! diff_allowed) 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, iterpool)); 845 knows_something = TRUE; 846 } 847 else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0) 848 { 849 SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool)); 850 if (performed_edit) 851 knows_something = TRUE; 852 } 853 else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 || 854 strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0) 855 { 856 if (desc->kind != svn_wc_conflict_kind_text) 857 { 858 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 859 _("Invalid option; can only " 860 "resolve text conflicts with " 861 "the internal merge tool." 862 "\n\n"))); 863 continue; 864 } 865 866 if (desc->base_abspath && desc->their_abspath && 867 desc->my_abspath && desc->merged_file) 868 { 869 svn_boolean_t remains_in_conflict; 870 871 SVN_ERR(svn_cl__merge_file(desc->base_abspath, 872 desc->their_abspath, 873 desc->my_abspath, 874 desc->merged_file, 875 desc->local_abspath, 876 b->path_prefix, 877 b->editor_cmd, 878 b->config, 879 &remains_in_conflict, 880 iterpool)); 881 knows_something = !remains_in_conflict; 882 } 883 else 884 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 885 _("Invalid option.\n\n"))); 886 } 887 else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0) 888 { 889 /* ### This check should be earlier as it's nasty to offer an option 890 * and then when the user chooses it say 'Invalid option'. */ 891 /* ### 'merged_file' shouldn't be necessary *before* we launch the 892 * resolver: it should be the *result* of doing so. */ 893 if (desc->base_abspath && desc->their_abspath && 894 desc->my_abspath && desc->merged_file) 895 { 896 SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool)); 897 if (performed_edit) 898 knows_something = TRUE; 899 } 900 else 901 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 902 _("Invalid option.\n\n"))); 903 } 904 else if (opt->choice != -1) 905 { 906 if ((opt->choice == svn_wc_conflict_choose_mine_conflict 907 || opt->choice == svn_wc_conflict_choose_theirs_conflict) 908 && desc->is_binary) 909 { 910 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 911 _("Invalid option; cannot choose " 912 "based on conflicts in a " 913 "binary file.\n\n"))); 914 continue; 915 } 916 917 /* We only allow the user accept the merged version of 918 the file if they've edited it, or at least looked at 919 the diff. */ 920 if (opt->choice == svn_wc_conflict_choose_merged 921 && ! knows_something) 922 { 923 SVN_ERR(svn_cmdline_fprintf( 924 stderr, iterpool, 925 _("Invalid option; use diff/edit/merge/launch " 926 "before choosing 'mark resolved'.\n\n"))); 927 continue; 928 } 929 930 result->choice = opt->choice; 931 if (performed_edit) 932 result->save_merged = TRUE; 933 break; 934 } 935 } 936 svn_pool_destroy(iterpool); 937 938 return SVN_NO_ERROR; 939} 940 941/* Ask the user what to do about the property conflict described by DESC. 942 * Return the answer in RESULT. B is the conflict baton for this 943 * conflict resolution session. 944 * SCRATCH_POOL is used for temporary allocations. */ 945static svn_error_t * 946handle_prop_conflict(svn_wc_conflict_result_t *result, 947 const svn_wc_conflict_description2_t *desc, 948 svn_cl__interactive_conflict_baton_t *b, 949 apr_pool_t *result_pool, 950 apr_pool_t *scratch_pool) 951{ 952 apr_pool_t *iterpool; 953 const char *message; 954 const char *merged_file_path = NULL; 955 svn_boolean_t resolved_allowed = FALSE; 956 957 /* ### Work around a historical bug in the provider: the path to the 958 * conflict description file was put in the 'theirs' field, and 959 * 'theirs' was put in the 'merged' field. */ 960 ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file; 961 ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL; 962 963 SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property); 964 965 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 966 _("Conflict for property '%s' discovered" 967 " on '%s'.\n"), 968 desc->property_name, 969 svn_cl__local_style_skip_ancestor( 970 b->path_prefix, desc->local_abspath, 971 scratch_pool))); 972 973 SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc, 974 scratch_pool)); 975 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message)); 976 977 iterpool = svn_pool_create(scratch_pool); 978 while (TRUE) 979 { 980 const resolver_option_t *opt; 981 const char *options[ARRAY_LEN(prop_conflict_options)]; 982 const char **next_option = options; 983 984 *next_option++ = "p"; 985 *next_option++ = "mf"; 986 *next_option++ = "tf"; 987 *next_option++ = "dc"; 988 *next_option++ = "e"; 989 if (resolved_allowed) 990 *next_option++ = "r"; 991 *next_option++ = "q"; 992 *next_option++ = "h"; 993 *next_option++ = NULL; 994 995 svn_pool_clear(iterpool); 996 997 SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb, 998 iterpool)); 999 if (! opt) 1000 continue; 1001 1002 if (strcmp(opt->code, "q") == 0) 1003 { 1004 result->choice = opt->choice; 1005 b->accept_which = svn_cl__accept_postpone; 1006 b->quit = TRUE; 1007 break; 1008 } 1009 else if (strcmp(opt->code, "dc") == 0) 1010 { 1011 SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool)); 1012 } 1013 else if (strcmp(opt->code, "e") == 0) 1014 { 1015 SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b, 1016 result_pool, scratch_pool)); 1017 resolved_allowed = (merged_file_path != NULL); 1018 } 1019 else if (strcmp(opt->code, "r") == 0) 1020 { 1021 if (! resolved_allowed) 1022 { 1023 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1024 _("Invalid option; please edit the property " 1025 "first.\n\n"))); 1026 continue; 1027 } 1028 1029 result->merged_file = merged_file_path; 1030 result->choice = svn_wc_conflict_choose_merged; 1031 break; 1032 } 1033 else if (opt->choice != -1) 1034 { 1035 result->choice = opt->choice; 1036 break; 1037 } 1038 } 1039 svn_pool_destroy(iterpool); 1040 1041 return SVN_NO_ERROR; 1042} 1043 1044/* Ask the user what to do about the tree conflict described by DESC. 1045 * Return the answer in RESULT. B is the conflict baton for this 1046 * conflict resolution session. 1047 * SCRATCH_POOL is used for temporary allocations. */ 1048static svn_error_t * 1049handle_tree_conflict(svn_wc_conflict_result_t *result, 1050 const svn_wc_conflict_description2_t *desc, 1051 svn_cl__interactive_conflict_baton_t *b, 1052 apr_pool_t *scratch_pool) 1053{ 1054 const char *readable_desc; 1055 apr_pool_t *iterpool; 1056 1057 SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( 1058 &readable_desc, desc, scratch_pool)); 1059 SVN_ERR(svn_cmdline_fprintf( 1060 stderr, scratch_pool, 1061 _("Tree conflict on '%s'\n > %s\n"), 1062 svn_cl__local_style_skip_ancestor(b->path_prefix, 1063 desc->local_abspath, 1064 scratch_pool), 1065 readable_desc)); 1066 1067 iterpool = svn_pool_create(scratch_pool); 1068 while (1) 1069 { 1070 const resolver_option_t *opt; 1071 const resolver_option_t *tc_opts; 1072 1073 svn_pool_clear(iterpool); 1074 1075 if (desc->operation == svn_wc_operation_update || 1076 desc->operation == svn_wc_operation_switch) 1077 { 1078 if (desc->reason == svn_wc_conflict_reason_moved_away) 1079 { 1080 if (desc->action == svn_wc_conflict_action_edit) 1081 tc_opts = tree_conflict_options_update_edit_moved_away; 1082 else 1083 tc_opts = tree_conflict_options_update_moved_away; 1084 } 1085 else if (desc->reason == svn_wc_conflict_reason_deleted) 1086 tc_opts = tree_conflict_options_update_deleted; 1087 else if (desc->reason == svn_wc_conflict_reason_replaced) 1088 tc_opts = tree_conflict_options_update_replaced; 1089 else 1090 tc_opts = tree_conflict_options; 1091 } 1092 else 1093 tc_opts = tree_conflict_options; 1094 1095 SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool)); 1096 if (! opt) 1097 continue; 1098 1099 if (strcmp(opt->code, "q") == 0) 1100 { 1101 result->choice = opt->choice; 1102 b->accept_which = svn_cl__accept_postpone; 1103 b->quit = TRUE; 1104 break; 1105 } 1106 else if (opt->choice != -1) 1107 { 1108 result->choice = opt->choice; 1109 break; 1110 } 1111 } 1112 svn_pool_destroy(iterpool); 1113 1114 return SVN_NO_ERROR; 1115} 1116 1117/* The body of svn_cl__conflict_func_interactive(). */ 1118static svn_error_t * 1119conflict_func_interactive(svn_wc_conflict_result_t **result, 1120 const svn_wc_conflict_description2_t *desc, 1121 void *baton, 1122 apr_pool_t *result_pool, 1123 apr_pool_t *scratch_pool) 1124{ 1125 svn_cl__interactive_conflict_baton_t *b = baton; 1126 svn_error_t *err; 1127 1128 /* Start out assuming we're going to postpone the conflict. */ 1129 *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, 1130 NULL, result_pool); 1131 1132 switch (b->accept_which) 1133 { 1134 case svn_cl__accept_invalid: 1135 case svn_cl__accept_unspecified: 1136 /* No (or no valid) --accept option, fall through to prompting. */ 1137 break; 1138 case svn_cl__accept_postpone: 1139 (*result)->choice = svn_wc_conflict_choose_postpone; 1140 return SVN_NO_ERROR; 1141 case svn_cl__accept_base: 1142 (*result)->choice = svn_wc_conflict_choose_base; 1143 return SVN_NO_ERROR; 1144 case svn_cl__accept_working: 1145 /* If the caller didn't merge the property values, then I guess 1146 * 'choose working' means 'choose mine'... */ 1147 if (! desc->merged_file) 1148 (*result)->merged_file = desc->my_abspath; 1149 (*result)->choice = svn_wc_conflict_choose_merged; 1150 return SVN_NO_ERROR; 1151 case svn_cl__accept_mine_conflict: 1152 (*result)->choice = svn_wc_conflict_choose_mine_conflict; 1153 return SVN_NO_ERROR; 1154 case svn_cl__accept_theirs_conflict: 1155 (*result)->choice = svn_wc_conflict_choose_theirs_conflict; 1156 return SVN_NO_ERROR; 1157 case svn_cl__accept_mine_full: 1158 (*result)->choice = svn_wc_conflict_choose_mine_full; 1159 return SVN_NO_ERROR; 1160 case svn_cl__accept_theirs_full: 1161 (*result)->choice = svn_wc_conflict_choose_theirs_full; 1162 return SVN_NO_ERROR; 1163 case svn_cl__accept_edit: 1164 if (desc->merged_file) 1165 { 1166 if (b->external_failed) 1167 { 1168 (*result)->choice = svn_wc_conflict_choose_postpone; 1169 return SVN_NO_ERROR; 1170 } 1171 1172 err = svn_cmdline__edit_file_externally(desc->merged_file, 1173 b->editor_cmd, b->config, 1174 scratch_pool); 1175 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) 1176 { 1177 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1178 err->message ? err->message : 1179 _("No editor found;" 1180 " leaving all conflicts."))); 1181 svn_error_clear(err); 1182 b->external_failed = TRUE; 1183 } 1184 else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 1185 { 1186 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1187 err->message ? err->message : 1188 _("Error running editor;" 1189 " leaving all conflicts."))); 1190 svn_error_clear(err); 1191 b->external_failed = TRUE; 1192 } 1193 else if (err) 1194 return svn_error_trace(err); 1195 (*result)->choice = svn_wc_conflict_choose_merged; 1196 return SVN_NO_ERROR; 1197 } 1198 /* else, fall through to prompting. */ 1199 break; 1200 case svn_cl__accept_launch: 1201 if (desc->base_abspath && desc->their_abspath 1202 && desc->my_abspath && desc->merged_file) 1203 { 1204 svn_boolean_t remains_in_conflict; 1205 1206 if (b->external_failed) 1207 { 1208 (*result)->choice = svn_wc_conflict_choose_postpone; 1209 return SVN_NO_ERROR; 1210 } 1211 1212 err = svn_cl__merge_file_externally(desc->base_abspath, 1213 desc->their_abspath, 1214 desc->my_abspath, 1215 desc->merged_file, 1216 desc->local_abspath, 1217 b->config, 1218 &remains_in_conflict, 1219 scratch_pool); 1220 if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) 1221 { 1222 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1223 err->message ? err->message : 1224 _("No merge tool found;" 1225 " leaving all conflicts."))); 1226 b->external_failed = TRUE; 1227 return svn_error_trace(err); 1228 } 1229 else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) 1230 { 1231 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1232 err->message ? err->message : 1233 _("Error running merge tool;" 1234 " leaving all conflicts."))); 1235 b->external_failed = TRUE; 1236 return svn_error_trace(err); 1237 } 1238 else if (err) 1239 return svn_error_trace(err); 1240 1241 if (remains_in_conflict) 1242 (*result)->choice = svn_wc_conflict_choose_postpone; 1243 else 1244 (*result)->choice = svn_wc_conflict_choose_merged; 1245 return SVN_NO_ERROR; 1246 } 1247 /* else, fall through to prompting. */ 1248 break; 1249 } 1250 1251 /* We're in interactive mode and either the user gave no --accept 1252 option or the option did not apply; let's prompt. */ 1253 1254 /* Handle the most common cases, which is either: 1255 1256 Conflicting edits on a file's text, or 1257 Conflicting edits on a property. 1258 */ 1259 if (((desc->kind == svn_wc_conflict_kind_text) 1260 && (desc->action == svn_wc_conflict_action_edit) 1261 && (desc->reason == svn_wc_conflict_reason_edited))) 1262 SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool)); 1263 else if (desc->kind == svn_wc_conflict_kind_property) 1264 SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool)); 1265 else if (desc->kind == svn_wc_conflict_kind_tree) 1266 SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool)); 1267 1268 else /* other types of conflicts -- do nothing about them. */ 1269 { 1270 (*result)->choice = svn_wc_conflict_choose_postpone; 1271 } 1272 1273 return SVN_NO_ERROR; 1274} 1275 1276svn_error_t * 1277svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result, 1278 const svn_wc_conflict_description2_t *desc, 1279 void *baton, 1280 apr_pool_t *result_pool, 1281 apr_pool_t *scratch_pool) 1282{ 1283 svn_cl__interactive_conflict_baton_t *b = baton; 1284 1285 SVN_ERR(conflict_func_interactive(result, desc, baton, 1286 result_pool, scratch_pool)); 1287 1288 /* If we are resolving a conflict, adjust the summary of conflicts. */ 1289 if ((*result)->choice != svn_wc_conflict_choose_postpone) 1290 { 1291 const char *local_path 1292 = svn_cl__local_style_skip_ancestor( 1293 b->path_prefix, desc->local_abspath, scratch_pool); 1294 1295 svn_cl__conflict_stats_resolved(b->conflict_stats, local_path, 1296 desc->kind); 1297 } 1298 return SVN_NO_ERROR; 1299} 1300