1251881Speter/* 2251881Speter * conflict-callbacks.c: conflict resolution callbacks specific to the 3251881Speter * commandline client. 4251881Speter * 5251881Speter * ==================================================================== 6251881Speter * Licensed to the Apache Software Foundation (ASF) under one 7251881Speter * or more contributor license agreements. See the NOTICE file 8251881Speter * distributed with this work for additional information 9251881Speter * regarding copyright ownership. The ASF licenses this file 10251881Speter * to you under the Apache License, Version 2.0 (the 11251881Speter * "License"); you may not use this file except in compliance 12251881Speter * with the License. You may obtain a copy of the License at 13251881Speter * 14251881Speter * http://www.apache.org/licenses/LICENSE-2.0 15251881Speter * 16251881Speter * Unless required by applicable law or agreed to in writing, 17251881Speter * software distributed under the License is distributed on an 18251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19251881Speter * KIND, either express or implied. See the License for the 20251881Speter * specific language governing permissions and limitations 21251881Speter * under the License. 22251881Speter * ==================================================================== 23251881Speter */ 24251881Speter 25251881Speter#include <apr_xlate.h> /* for APR_LOCALE_CHARSET */ 26251881Speter 27251881Speter#define APR_WANT_STRFUNC 28251881Speter#include <apr_want.h> 29251881Speter 30251881Speter#include "svn_hash.h" 31251881Speter#include "svn_cmdline.h" 32251881Speter#include "svn_client.h" 33251881Speter#include "svn_dirent_uri.h" 34251881Speter#include "svn_types.h" 35251881Speter#include "svn_pools.h" 36251881Speter#include "svn_sorts.h" 37251881Speter#include "svn_utf.h" 38251881Speter 39251881Speter#include "cl.h" 40251881Speter#include "cl-conflicts.h" 41251881Speter 42251881Speter#include "private/svn_cmdline_private.h" 43251881Speter 44251881Speter#include "svn_private_config.h" 45251881Speter 46251881Speter#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0]))) 47251881Speter 48251881Speter 49251881Speter 50251881Speterstruct svn_cl__interactive_conflict_baton_t { 51251881Speter svn_cl__accept_t accept_which; 52251881Speter apr_hash_t *config; 53251881Speter const char *editor_cmd; 54251881Speter svn_boolean_t external_failed; 55251881Speter svn_cmdline_prompt_baton_t *pb; 56251881Speter const char *path_prefix; 57251881Speter svn_boolean_t quit; 58251881Speter svn_cl__conflict_stats_t *conflict_stats; 59251881Speter}; 60251881Speter 61251881Spetersvn_error_t * 62251881Spetersvn_cl__get_conflict_func_interactive_baton( 63251881Speter svn_cl__interactive_conflict_baton_t **b, 64251881Speter svn_cl__accept_t accept_which, 65251881Speter apr_hash_t *config, 66251881Speter const char *editor_cmd, 67251881Speter svn_cl__conflict_stats_t *conflict_stats, 68251881Speter svn_cancel_func_t cancel_func, 69251881Speter void *cancel_baton, 70251881Speter apr_pool_t *result_pool) 71251881Speter{ 72251881Speter svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb)); 73251881Speter pb->cancel_func = cancel_func; 74251881Speter pb->cancel_baton = cancel_baton; 75251881Speter 76251881Speter *b = apr_palloc(result_pool, sizeof(**b)); 77251881Speter (*b)->accept_which = accept_which; 78251881Speter (*b)->config = config; 79251881Speter (*b)->editor_cmd = editor_cmd; 80251881Speter (*b)->external_failed = FALSE; 81251881Speter (*b)->pb = pb; 82251881Speter SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool)); 83251881Speter (*b)->quit = FALSE; 84251881Speter (*b)->conflict_stats = conflict_stats; 85251881Speter 86251881Speter return SVN_NO_ERROR; 87251881Speter} 88251881Speter 89251881Spetersvn_cl__accept_t 90251881Spetersvn_cl__accept_from_word(const char *word) 91251881Speter{ 92251881Speter /* Shorthand options are consistent with svn_cl__conflict_handler(). */ 93251881Speter if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0 94251881Speter || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0) 95251881Speter return svn_cl__accept_postpone; 96251881Speter if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0) 97251881Speter /* ### shorthand? */ 98251881Speter return svn_cl__accept_base; 99251881Speter if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0) 100251881Speter /* ### shorthand? */ 101251881Speter return svn_cl__accept_working; 102251881Speter if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0 103251881Speter || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0) 104251881Speter return svn_cl__accept_mine_conflict; 105251881Speter if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0 106251881Speter || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0) 107251881Speter return svn_cl__accept_theirs_conflict; 108251881Speter if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0 109251881Speter || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0) 110251881Speter return svn_cl__accept_mine_full; 111251881Speter if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0 112251881Speter || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0) 113251881Speter return svn_cl__accept_theirs_full; 114251881Speter if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0 115251881Speter || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0) 116251881Speter return svn_cl__accept_edit; 117251881Speter if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0 118251881Speter || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0) 119251881Speter return svn_cl__accept_launch; 120251881Speter /* word is an invalid action. */ 121251881Speter return svn_cl__accept_invalid; 122251881Speter} 123251881Speter 124251881Speter 125251881Speter/* Print on stdout a diff that shows incoming conflicting changes 126251881Speter * corresponding to the conflict described in DESC. */ 127251881Speterstatic svn_error_t * 128251881Spetershow_diff(const svn_wc_conflict_description2_t *desc, 129251881Speter const char *path_prefix, 130251881Speter apr_pool_t *pool) 131251881Speter{ 132251881Speter const char *path1, *path2; 133251881Speter const char *label1, *label2; 134251881Speter svn_diff_t *diff; 135251881Speter svn_stream_t *output; 136251881Speter svn_diff_file_options_t *options; 137251881Speter 138251881Speter if (desc->merged_file) 139251881Speter { 140251881Speter /* For conflicts recorded by the 'merge' operation, show a diff between 141251881Speter * 'mine' (the working version of the file as it appeared before the 142251881Speter * 'merge' operation was run) and 'merged' (the version of the file 143251881Speter * as it appears after the merge operation). 144251881Speter * 145251881Speter * For conflicts recorded by the 'update' and 'switch' operations, 146251881Speter * show a diff beween 'theirs' (the new pristine version of the 147251881Speter * file) and 'merged' (the version of the file as it appears with 148251881Speter * local changes merged with the new pristine version). 149251881Speter * 150251881Speter * This way, the diff is always minimal and clearly identifies changes 151251881Speter * brought into the working copy by the update/switch/merge operation. */ 152251881Speter if (desc->operation == svn_wc_operation_merge) 153251881Speter { 154251881Speter path1 = desc->my_abspath; 155251881Speter label1 = _("MINE"); 156251881Speter } 157251881Speter else 158251881Speter { 159251881Speter path1 = desc->their_abspath; 160251881Speter label1 = _("THEIRS"); 161251881Speter } 162251881Speter path2 = desc->merged_file; 163251881Speter label2 = _("MERGED"); 164251881Speter } 165251881Speter else 166251881Speter { 167251881Speter /* There's no merged file, but we can show the 168251881Speter difference between mine and theirs. */ 169251881Speter path1 = desc->their_abspath; 170251881Speter label1 = _("THEIRS"); 171251881Speter path2 = desc->my_abspath; 172251881Speter label2 = _("MINE"); 173251881Speter } 174251881Speter 175251881Speter label1 = apr_psprintf(pool, "%s\t- %s", 176251881Speter svn_cl__local_style_skip_ancestor( 177251881Speter path_prefix, path1, pool), label1); 178251881Speter label2 = apr_psprintf(pool, "%s\t- %s", 179251881Speter svn_cl__local_style_skip_ancestor( 180251881Speter path_prefix, path2, pool), label2); 181251881Speter 182251881Speter options = svn_diff_file_options_create(pool); 183251881Speter options->ignore_eol_style = TRUE; 184251881Speter SVN_ERR(svn_stream_for_stdout(&output, pool)); 185251881Speter SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2, 186251881Speter options, pool)); 187251881Speter return svn_diff_file_output_unified3(output, diff, 188251881Speter path1, path2, 189251881Speter label1, label2, 190251881Speter APR_LOCALE_CHARSET, 191251881Speter NULL, FALSE, 192251881Speter pool); 193251881Speter} 194251881Speter 195251881Speter 196251881Speter/* Print on stdout just the conflict hunks of a diff among the 'base', 'their' 197251881Speter * and 'my' files of DESC. */ 198251881Speterstatic svn_error_t * 199251881Spetershow_conflicts(const svn_wc_conflict_description2_t *desc, 200251881Speter apr_pool_t *pool) 201251881Speter{ 202251881Speter svn_diff_t *diff; 203251881Speter svn_stream_t *output; 204251881Speter svn_diff_file_options_t *options; 205251881Speter 206251881Speter options = svn_diff_file_options_create(pool); 207251881Speter options->ignore_eol_style = TRUE; 208251881Speter SVN_ERR(svn_stream_for_stdout(&output, pool)); 209251881Speter SVN_ERR(svn_diff_file_diff3_2(&diff, 210251881Speter desc->base_abspath, 211251881Speter desc->my_abspath, 212251881Speter desc->their_abspath, 213251881Speter options, pool)); 214251881Speter /* ### Consider putting the markers/labels from 215251881Speter ### svn_wc__merge_internal in the conflict description. */ 216251881Speter return svn_diff_file_output_merge2(output, diff, 217251881Speter desc->base_abspath, 218251881Speter desc->my_abspath, 219251881Speter desc->their_abspath, 220251881Speter _("||||||| ORIGINAL"), 221251881Speter _("<<<<<<< MINE (select with 'mc')"), 222251881Speter _(">>>>>>> THEIRS (select with 'tc')"), 223251881Speter "=======", 224251881Speter svn_diff_conflict_display_only_conflicts, 225251881Speter pool); 226251881Speter} 227251881Speter 228251881Speter/* Perform a 3-way merge of the conflicting values of a property, 229251881Speter * and write the result to the OUTPUT stream. 230251881Speter * 231251881Speter * If MERGED_ABSPATH is non-NULL, use it as 'my' version instead of 232251881Speter * DESC->MY_ABSPATH. 233251881Speter * 234251881Speter * Assume the values are printable UTF-8 text. 235251881Speter */ 236251881Speterstatic svn_error_t * 237251881Spetermerge_prop_conflict(svn_stream_t *output, 238251881Speter const svn_wc_conflict_description2_t *desc, 239251881Speter const char *merged_abspath, 240251881Speter apr_pool_t *pool) 241251881Speter{ 242251881Speter const char *base_abspath = desc->base_abspath; 243251881Speter const char *my_abspath = desc->my_abspath; 244251881Speter const char *their_abspath = desc->their_abspath; 245251881Speter svn_diff_file_options_t *options = svn_diff_file_options_create(pool); 246251881Speter svn_diff_t *diff; 247251881Speter 248251881Speter /* If any of the property values is missing, use an empty file instead 249251881Speter * for the purpose of showing a diff. */ 250251881Speter if (! base_abspath || ! my_abspath || ! their_abspath) 251251881Speter { 252251881Speter const char *empty_file; 253251881Speter 254251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file, 255251881Speter NULL, svn_io_file_del_on_pool_cleanup, 256251881Speter pool, pool)); 257251881Speter if (! base_abspath) 258251881Speter base_abspath = empty_file; 259251881Speter if (! my_abspath) 260251881Speter my_abspath = empty_file; 261251881Speter if (! their_abspath) 262251881Speter their_abspath = empty_file; 263251881Speter } 264251881Speter 265251881Speter options->ignore_eol_style = TRUE; 266251881Speter SVN_ERR(svn_diff_file_diff3_2(&diff, 267251881Speter base_abspath, 268251881Speter merged_abspath ? merged_abspath : my_abspath, 269251881Speter their_abspath, 270251881Speter options, pool)); 271251881Speter SVN_ERR(svn_diff_file_output_merge2(output, diff, 272251881Speter base_abspath, 273251881Speter merged_abspath ? merged_abspath 274251881Speter : my_abspath, 275251881Speter their_abspath, 276251881Speter _("||||||| ORIGINAL"), 277251881Speter _("<<<<<<< MINE"), 278251881Speter _(">>>>>>> THEIRS"), 279251881Speter "=======", 280251881Speter svn_diff_conflict_display_modified_original_latest, 281251881Speter pool)); 282251881Speter 283251881Speter return SVN_NO_ERROR; 284251881Speter} 285251881Speter 286251881Speter/* Display the conflicting values of a property as a 3-way diff. 287251881Speter * 288251881Speter * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of 289251881Speter * DESC->MY_ABSPATH. 290251881Speter * 291251881Speter * Assume the values are printable UTF-8 text. 292251881Speter */ 293251881Speterstatic svn_error_t * 294251881Spetershow_prop_conflict(const svn_wc_conflict_description2_t *desc, 295251881Speter const char *merged_abspath, 296251881Speter apr_pool_t *pool) 297251881Speter{ 298251881Speter svn_stream_t *output; 299251881Speter 300251881Speter SVN_ERR(svn_stream_for_stdout(&output, pool)); 301251881Speter SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, pool)); 302251881Speter 303251881Speter return SVN_NO_ERROR; 304251881Speter} 305251881Speter 306251881Speter/* Run an external editor, passing it the MERGED_FILE, or, if the 307251881Speter * 'merged' file is null, return an error. The tool to use is determined by 308251881Speter * B->editor_cmd, B->config and environment variables; see 309251881Speter * svn_cl__edit_file_externally() for details. 310251881Speter * 311251881Speter * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not 312251881Speter * configured or cannot run, do not touch *PERFORMED_EDIT, report the error 313251881Speter * on stderr, and return SVN_NO_ERROR; if any other error is encountered, 314251881Speter * return that error. */ 315251881Speterstatic svn_error_t * 316251881Speteropen_editor(svn_boolean_t *performed_edit, 317251881Speter const char *merged_file, 318251881Speter svn_cl__interactive_conflict_baton_t *b, 319251881Speter apr_pool_t *pool) 320251881Speter{ 321251881Speter svn_error_t *err; 322251881Speter 323251881Speter if (merged_file) 324251881Speter { 325251881Speter err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd, 326251881Speter b->config, pool); 327251881Speter if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) 328251881Speter { 329251881Speter svn_error_t *root_err = svn_error_root_cause(err); 330251881Speter 331251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", 332251881Speter root_err->message ? root_err->message : 333251881Speter _("No editor found."))); 334251881Speter svn_error_clear(err); 335251881Speter } 336251881Speter else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 337251881Speter { 338251881Speter svn_error_t *root_err = svn_error_root_cause(err); 339251881Speter 340251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", 341251881Speter root_err->message ? root_err->message : 342251881Speter _("Error running editor."))); 343251881Speter svn_error_clear(err); 344251881Speter } 345251881Speter else if (err) 346251881Speter return svn_error_trace(err); 347251881Speter else 348251881Speter *performed_edit = TRUE; 349251881Speter } 350251881Speter else 351251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, pool, 352251881Speter _("Invalid option; there's no " 353251881Speter "merged version to edit.\n\n"))); 354251881Speter 355251881Speter return SVN_NO_ERROR; 356251881Speter} 357251881Speter 358251881Speter/* Run an external editor, passing it the 'merged' property in DESC. 359251881Speter * The tool to use is determined by B->editor_cmd, B->config and 360251881Speter * environment variables; see svn_cl__edit_file_externally() for details. */ 361251881Speterstatic svn_error_t * 362251881Speteredit_prop_conflict(const char **merged_file_path, 363251881Speter const svn_wc_conflict_description2_t *desc, 364251881Speter svn_cl__interactive_conflict_baton_t *b, 365251881Speter apr_pool_t *result_pool, 366251881Speter apr_pool_t *scratch_pool) 367251881Speter{ 368251881Speter apr_file_t *file; 369251881Speter const char *file_path; 370251881Speter svn_boolean_t performed_edit = FALSE; 371251881Speter svn_stream_t *merged_prop; 372251881Speter 373251881Speter SVN_ERR(svn_io_open_unique_file3(&file, &file_path, NULL, 374251881Speter svn_io_file_del_on_pool_cleanup, 375251881Speter result_pool, scratch_pool)); 376251881Speter merged_prop = svn_stream_from_aprfile2(file, TRUE /* disown */, 377251881Speter scratch_pool); 378251881Speter SVN_ERR(merge_prop_conflict(merged_prop, desc, NULL, scratch_pool)); 379251881Speter SVN_ERR(svn_stream_close(merged_prop)); 380251881Speter SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool)); 381251881Speter SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool)); 382251881Speter *merged_file_path = (performed_edit ? file_path : NULL); 383251881Speter 384251881Speter return SVN_NO_ERROR; 385251881Speter} 386251881Speter 387251881Speter/* Run an external merge tool, passing it the 'base', 'their', 'my' and 388251881Speter * 'merged' files in DESC. The tool to use is determined by B->config and 389251881Speter * environment variables; see svn_cl__merge_file_externally() for details. 390251881Speter * 391251881Speter * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not 392251881Speter * configured or cannot run, do not touch *PERFORMED_EDIT, report the error 393251881Speter * on stderr, and return SVN_NO_ERROR; if any other error is encountered, 394251881Speter * return that error. */ 395251881Speterstatic svn_error_t * 396251881Speterlaunch_resolver(svn_boolean_t *performed_edit, 397251881Speter const svn_wc_conflict_description2_t *desc, 398251881Speter svn_cl__interactive_conflict_baton_t *b, 399251881Speter apr_pool_t *pool) 400251881Speter{ 401251881Speter svn_error_t *err; 402251881Speter 403251881Speter err = svn_cl__merge_file_externally(desc->base_abspath, desc->their_abspath, 404251881Speter desc->my_abspath, desc->merged_file, 405251881Speter desc->local_abspath, b->config, NULL, 406251881Speter pool); 407251881Speter if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) 408251881Speter { 409251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", 410251881Speter err->message ? err->message : 411251881Speter _("No merge tool found, " 412251881Speter "try '(m) merge' instead.\n"))); 413251881Speter svn_error_clear(err); 414251881Speter } 415251881Speter else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) 416251881Speter { 417251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", 418251881Speter err->message ? err->message : 419251881Speter _("Error running merge tool, " 420251881Speter "try '(m) merge' instead."))); 421251881Speter svn_error_clear(err); 422251881Speter } 423251881Speter else if (err) 424251881Speter return svn_error_trace(err); 425251881Speter else if (performed_edit) 426251881Speter *performed_edit = TRUE; 427251881Speter 428251881Speter return SVN_NO_ERROR; 429251881Speter} 430251881Speter 431251881Speter 432251881Speter/* Maximum line length for the prompt string. */ 433251881Speter#define MAX_PROMPT_WIDTH 70 434251881Speter 435251881Speter/* Description of a resolver option */ 436251881Spetertypedef struct resolver_option_t 437251881Speter{ 438251881Speter const char *code; /* one or two characters */ 439251881Speter const char *short_desc; /* label in prompt (localized) */ 440251881Speter const char *long_desc; /* longer description (localized) */ 441251881Speter svn_wc_conflict_choice_t choice; /* or -1 if not a simple choice */ 442251881Speter} resolver_option_t; 443251881Speter 444251881Speter/* Resolver options for a text conflict */ 445251881Speter/* (opt->code == "" causes a blank line break in help_string()) */ 446251881Speterstatic const resolver_option_t text_conflict_options[] = 447251881Speter{ 448251881Speter /* Translators: keep long_desc below 70 characters (wrap with a left 449251881Speter margin of 9 spaces if needed); don't translate the words within square 450251881Speter brackets. */ 451251881Speter { "e", N_("edit file"), N_("change merged file in an editor" 452251881Speter " [edit]"), 453251881Speter -1 }, 454251881Speter { "df", N_("show diff"), N_("show all changes made to merged file"), 455251881Speter -1 }, 456253734Speter { "r", N_("mark resolved"), N_("accept merged version of file"), 457251881Speter svn_wc_conflict_choose_merged }, 458251881Speter { "", "", "", svn_wc_conflict_choose_unspecified }, 459251881Speter { "dc", N_("display conflict"), N_("show all conflicts " 460251881Speter "(ignoring merged version)"), -1 }, 461251881Speter { "mc", N_("my side of conflict"), N_("accept my version for all conflicts " 462251881Speter "(same) [mine-conflict]"), 463251881Speter svn_wc_conflict_choose_mine_conflict }, 464251881Speter { "tc", N_("their side of conflict"), N_("accept their version for all " 465251881Speter "conflicts (same)" 466251881Speter " [theirs-conflict]"), 467251881Speter svn_wc_conflict_choose_theirs_conflict }, 468251881Speter { "", "", "", svn_wc_conflict_choose_unspecified }, 469251881Speter { "mf", N_("my version"), N_("accept my version of entire file (even " 470251881Speter "non-conflicts) [mine-full]"), 471251881Speter svn_wc_conflict_choose_mine_full }, 472251881Speter { "tf", N_("their version"), N_("accept their version of entire file " 473251881Speter "(same) [theirs-full]"), 474251881Speter svn_wc_conflict_choose_theirs_full }, 475251881Speter { "", "", "", svn_wc_conflict_choose_unspecified }, 476251881Speter { "m", N_("merge"), N_("use internal merge tool to resolve " 477251881Speter "conflict"), -1 }, 478251881Speter { "l", N_("launch tool"), N_("launch external tool to resolve " 479251881Speter "conflict [launch]"), -1 }, 480253734Speter { "p", N_("postpone"), N_("mark the conflict to be resolved later" 481253734Speter " [postpone]"), 482253734Speter svn_wc_conflict_choose_postpone }, 483251881Speter { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 484251881Speter svn_wc_conflict_choose_postpone }, 485251881Speter { "s", N_("show all options"), N_("show this list (also 'h', '?')"), -1 }, 486251881Speter { NULL } 487251881Speter}; 488251881Speter 489251881Speter/* Resolver options for a property conflict */ 490251881Speterstatic const resolver_option_t prop_conflict_options[] = 491251881Speter{ 492262253Speter { "mf", N_("my version"), N_("accept my version of entire property (even " 493251881Speter "non-conflicts) [mine-full]"), 494251881Speter svn_wc_conflict_choose_mine_full }, 495262253Speter { "tf", N_("their version"), N_("accept their version of entire property " 496251881Speter "(same) [theirs-full]"), 497251881Speter svn_wc_conflict_choose_theirs_full }, 498251881Speter { "dc", N_("display conflict"), N_("show conflicts in this property"), -1 }, 499251881Speter { "e", N_("edit property"), N_("change merged property value in an editor" 500251881Speter " [edit]"), -1 }, 501253734Speter { "r", N_("mark resolved"), N_("accept edited version of property"), 502251881Speter svn_wc_conflict_choose_merged }, 503253734Speter { "p", N_("postpone"), N_("mark the conflict to be resolved later" 504253734Speter " [postpone]"), 505253734Speter svn_wc_conflict_choose_postpone }, 506251881Speter { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 507251881Speter svn_wc_conflict_choose_postpone }, 508251881Speter { "h", N_("help"), N_("show this help (also '?')"), -1 }, 509251881Speter { NULL } 510251881Speter}; 511251881Speter 512251881Speter/* Resolver options for a tree conflict */ 513251881Speterstatic const resolver_option_t tree_conflict_options[] = 514251881Speter{ 515253734Speter { "r", N_("mark resolved"), N_("accept current working copy state"), 516253734Speter svn_wc_conflict_choose_merged }, 517251881Speter { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 518251881Speter svn_wc_conflict_choose_postpone }, 519251881Speter { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 520251881Speter svn_wc_conflict_choose_postpone }, 521251881Speter { "h", N_("help"), N_("show this help (also '?')"), -1 }, 522251881Speter { NULL } 523251881Speter}; 524251881Speter 525251881Speterstatic const resolver_option_t tree_conflict_options_update_moved_away[] = 526251881Speter{ 527253734Speter { "mc", N_("apply update (recommended)"), 528253734Speter N_("apply update to the move destination" 529253734Speter " [mine-conflict]"), 530253734Speter svn_wc_conflict_choose_mine_conflict }, 531253734Speter { "r", N_("discard update (breaks move)"), N_("discard update, mark " 532253734Speter "resolved, the move will " 533253734Speter "will become a copy"), 534253734Speter svn_wc_conflict_choose_merged }, 535251881Speter { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 536251881Speter svn_wc_conflict_choose_postpone }, 537253734Speter { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 538253734Speter svn_wc_conflict_choose_postpone }, 539253734Speter { "h", N_("help"), N_("show this help (also '?')"), -1 }, 540253734Speter { NULL } 541253734Speter}; 542253734Speter 543253734Speterstatic const resolver_option_t tree_conflict_options_update_edit_moved_away[] = 544253734Speter{ 545253734Speter { "mc", N_("apply update to move destination"), 546253734Speter N_("apply incoming update to move destination" 547253734Speter " [mine-conflict]"), 548251881Speter svn_wc_conflict_choose_mine_conflict }, 549253734Speter { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 550253734Speter svn_wc_conflict_choose_postpone }, 551251881Speter { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 552251881Speter svn_wc_conflict_choose_postpone }, 553251881Speter { "h", N_("help"), N_("show this help (also '?')"), -1 }, 554251881Speter { NULL } 555251881Speter}; 556251881Speter 557251881Speterstatic const resolver_option_t tree_conflict_options_update_deleted[] = 558251881Speter{ 559253734Speter { "mc", N_("keep affected local moves"), N_("keep any local moves affected " 560253734Speter "by this deletion [mine-conflict]"), 561253734Speter svn_wc_conflict_choose_mine_conflict }, 562253734Speter { "r", N_("mark resolved (breaks moves)"), N_("mark resolved, any affected " 563253734Speter "moves will become copies"), 564253734Speter svn_wc_conflict_choose_merged }, 565251881Speter { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 566251881Speter svn_wc_conflict_choose_postpone }, 567251881Speter { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 568251881Speter svn_wc_conflict_choose_postpone }, 569251881Speter { "h", N_("help"), N_("show this help (also '?')"), -1 }, 570251881Speter { NULL } 571251881Speter}; 572251881Speter 573251881Speterstatic const resolver_option_t tree_conflict_options_update_replaced[] = 574251881Speter{ 575253734Speter { "mc", N_("keep affected local moves"), N_("keep any moves affected by this " 576253734Speter "replacement [mine-conflict]"), 577253734Speter svn_wc_conflict_choose_mine_conflict }, 578253734Speter { "r", N_("mark resolved (breaks moves)"), N_("mark resolved (any affected " 579253734Speter "moves will become copies)"), 580253734Speter svn_wc_conflict_choose_merged }, 581251881Speter { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), 582251881Speter svn_wc_conflict_choose_postpone }, 583251881Speter { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), 584251881Speter svn_wc_conflict_choose_postpone }, 585251881Speter { "h", N_("help"), N_("show this help (also '?')"), -1 }, 586251881Speter { NULL } 587251881Speter}; 588251881Speter 589251881Speter 590251881Speter/* Return a pointer to the option description in OPTIONS matching the 591251881Speter * one- or two-character OPTION_CODE. Return NULL if not found. */ 592251881Speterstatic const resolver_option_t * 593251881Speterfind_option(const resolver_option_t *options, 594251881Speter const char *option_code) 595251881Speter{ 596251881Speter const resolver_option_t *opt; 597251881Speter 598251881Speter for (opt = options; opt->code; opt++) 599251881Speter { 600251881Speter /* Ignore code "" (blank lines) which is not a valid answer. */ 601251881Speter if (opt->code[0] && strcmp(opt->code, option_code) == 0) 602251881Speter return opt; 603251881Speter } 604251881Speter return NULL; 605251881Speter} 606251881Speter 607251881Speter/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is 608251881Speter * non-null, select only the options whose codes are mentioned in it. */ 609251881Speterstatic const char * 610251881Speterprompt_string(const resolver_option_t *options, 611251881Speter const char *const *option_codes, 612251881Speter apr_pool_t *pool) 613251881Speter{ 614251881Speter const char *result = _("Select:"); 615251881Speter int left_margin = svn_utf_cstring_utf8_width(result); 616251881Speter const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, ""); 617251881Speter int this_line_len = left_margin; 618251881Speter svn_boolean_t first = TRUE; 619251881Speter 620251881Speter while (1) 621251881Speter { 622251881Speter const resolver_option_t *opt; 623251881Speter const char *s; 624251881Speter int slen; 625251881Speter 626251881Speter if (option_codes) 627251881Speter { 628251881Speter if (! *option_codes) 629251881Speter break; 630251881Speter opt = find_option(options, *option_codes++); 631251881Speter } 632251881Speter else 633251881Speter { 634251881Speter opt = options++; 635251881Speter if (! opt->code) 636251881Speter break; 637251881Speter } 638251881Speter 639251881Speter if (! first) 640251881Speter result = apr_pstrcat(pool, result, ",", (char *)NULL); 641251881Speter s = apr_psprintf(pool, _(" (%s) %s"), 642251881Speter opt->code, _(opt->short_desc)); 643251881Speter slen = svn_utf_cstring_utf8_width(s); 644251881Speter /* Break the line if adding the next option would make it too long */ 645251881Speter if (this_line_len + slen > MAX_PROMPT_WIDTH) 646251881Speter { 647251881Speter result = apr_pstrcat(pool, result, line_sep, (char *)NULL); 648251881Speter this_line_len = left_margin; 649251881Speter } 650251881Speter result = apr_pstrcat(pool, result, s, (char *)NULL); 651251881Speter this_line_len += slen; 652251881Speter first = FALSE; 653251881Speter } 654251881Speter return apr_pstrcat(pool, result, ": ", (char *)NULL); 655251881Speter} 656251881Speter 657251881Speter/* Return a help string listing the OPTIONS. */ 658251881Speterstatic const char * 659251881Speterhelp_string(const resolver_option_t *options, 660251881Speter apr_pool_t *pool) 661251881Speter{ 662251881Speter const char *result = ""; 663251881Speter const resolver_option_t *opt; 664251881Speter 665251881Speter for (opt = options; opt->code; opt++) 666251881Speter { 667251881Speter /* Append a line describing OPT, or a blank line if its code is "". */ 668251881Speter if (opt->code[0]) 669251881Speter { 670251881Speter const char *s = apr_psprintf(pool, " (%s)", opt->code); 671251881Speter 672251881Speter result = apr_psprintf(pool, "%s%-6s - %s\n", 673251881Speter result, s, _(opt->long_desc)); 674251881Speter } 675251881Speter else 676251881Speter { 677251881Speter result = apr_pstrcat(pool, result, "\n", (char *)NULL); 678251881Speter } 679251881Speter } 680251881Speter result = apr_pstrcat(pool, result, 681251881Speter _("Words in square brackets are the corresponding " 682251881Speter "--accept option arguments.\n"), 683251881Speter (char *)NULL); 684251881Speter return result; 685251881Speter} 686251881Speter 687251881Speter/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed 688251881Speter * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen 689251881Speter * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to 690251881Speter * NULL if the answer was not one of them. 691251881Speter * 692251881Speter * If the answer is the (globally recognized) 'help' option, then display 693251881Speter * the help (on stderr) and return with *OPT == NULL. 694251881Speter */ 695251881Speterstatic svn_error_t * 696251881Speterprompt_user(const resolver_option_t **opt, 697251881Speter const resolver_option_t *conflict_options, 698251881Speter const char *const *options_to_show, 699251881Speter void *prompt_baton, 700251881Speter apr_pool_t *scratch_pool) 701251881Speter{ 702251881Speter const char *prompt 703251881Speter = prompt_string(conflict_options, options_to_show, scratch_pool); 704251881Speter const char *answer; 705251881Speter 706251881Speter SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool)); 707251881Speter if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0) 708251881Speter { 709251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", 710251881Speter help_string(conflict_options, 711251881Speter scratch_pool))); 712251881Speter *opt = NULL; 713251881Speter } 714251881Speter else 715251881Speter { 716251881Speter *opt = find_option(conflict_options, answer); 717251881Speter if (! *opt) 718251881Speter { 719251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 720251881Speter _("Unrecognized option.\n\n"))); 721251881Speter } 722251881Speter } 723251881Speter return SVN_NO_ERROR; 724251881Speter} 725251881Speter 726251881Speter/* Ask the user what to do about the text conflict described by DESC. 727251881Speter * Return the answer in RESULT. B is the conflict baton for this 728251881Speter * conflict resolution session. 729251881Speter * SCRATCH_POOL is used for temporary allocations. */ 730251881Speterstatic svn_error_t * 731251881Speterhandle_text_conflict(svn_wc_conflict_result_t *result, 732251881Speter const svn_wc_conflict_description2_t *desc, 733251881Speter svn_cl__interactive_conflict_baton_t *b, 734251881Speter apr_pool_t *scratch_pool) 735251881Speter{ 736251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 737251881Speter svn_boolean_t diff_allowed = FALSE; 738251881Speter /* Have they done something that might have affected the merged 739251881Speter file (so that we need to save a .edited copy)? */ 740251881Speter svn_boolean_t performed_edit = FALSE; 741251881Speter /* Have they done *something* (edit, look at diff, etc) to 742251881Speter give them a rational basis for choosing (r)esolved? */ 743251881Speter svn_boolean_t knows_something = FALSE; 744251881Speter 745251881Speter SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text); 746251881Speter 747251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 748251881Speter _("Conflict discovered in file '%s'.\n"), 749251881Speter svn_cl__local_style_skip_ancestor( 750251881Speter b->path_prefix, desc->local_abspath, 751251881Speter scratch_pool))); 752251881Speter 753251881Speter /* Diffing can happen between base and merged, to show conflict 754251881Speter markers to the user (this is the typical 3-way merge 755251881Speter scenario), or if no base is available, we can show a diff 756251881Speter between mine and theirs. */ 757251881Speter if ((desc->merged_file && desc->base_abspath) 758251881Speter || (!desc->base_abspath && desc->my_abspath && desc->their_abspath)) 759251881Speter diff_allowed = TRUE; 760251881Speter 761251881Speter while (TRUE) 762251881Speter { 763251881Speter const char *options[ARRAY_LEN(text_conflict_options)]; 764251881Speter const char **next_option = options; 765251881Speter const resolver_option_t *opt; 766251881Speter 767251881Speter svn_pool_clear(iterpool); 768251881Speter 769251881Speter *next_option++ = "p"; 770251881Speter if (diff_allowed) 771251881Speter { 772251881Speter *next_option++ = "df"; 773251881Speter *next_option++ = "e"; 774251881Speter *next_option++ = "m"; 775251881Speter 776251881Speter if (knows_something) 777251881Speter *next_option++ = "r"; 778251881Speter 779251881Speter if (! desc->is_binary) 780251881Speter { 781251881Speter *next_option++ = "mc"; 782251881Speter *next_option++ = "tc"; 783251881Speter } 784251881Speter } 785251881Speter else 786251881Speter { 787251881Speter if (knows_something) 788251881Speter *next_option++ = "r"; 789251881Speter *next_option++ = "mf"; 790251881Speter *next_option++ = "tf"; 791251881Speter } 792251881Speter *next_option++ = "s"; 793251881Speter *next_option++ = NULL; 794251881Speter 795251881Speter SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->pb, 796251881Speter iterpool)); 797251881Speter if (! opt) 798251881Speter continue; 799251881Speter 800251881Speter if (strcmp(opt->code, "q") == 0) 801251881Speter { 802251881Speter result->choice = opt->choice; 803251881Speter b->accept_which = svn_cl__accept_postpone; 804251881Speter b->quit = TRUE; 805251881Speter break; 806251881Speter } 807251881Speter else if (strcmp(opt->code, "s") == 0) 808251881Speter { 809251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", 810251881Speter help_string(text_conflict_options, 811251881Speter iterpool))); 812251881Speter } 813251881Speter else if (strcmp(opt->code, "dc") == 0) 814251881Speter { 815251881Speter if (desc->is_binary) 816251881Speter { 817251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 818251881Speter _("Invalid option; cannot " 819251881Speter "display conflicts for a " 820251881Speter "binary file.\n\n"))); 821251881Speter continue; 822251881Speter } 823251881Speter else if (! (desc->my_abspath && desc->base_abspath && 824251881Speter desc->their_abspath)) 825251881Speter { 826251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 827251881Speter _("Invalid option; original " 828251881Speter "files not available.\n\n"))); 829251881Speter continue; 830251881Speter } 831251881Speter SVN_ERR(show_conflicts(desc, iterpool)); 832251881Speter knows_something = TRUE; 833251881Speter } 834251881Speter else if (strcmp(opt->code, "df") == 0) 835251881Speter { 836251881Speter if (! diff_allowed) 837251881Speter { 838251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 839251881Speter _("Invalid option; there's no " 840251881Speter "merged version to diff.\n\n"))); 841251881Speter continue; 842251881Speter } 843251881Speter 844251881Speter SVN_ERR(show_diff(desc, b->path_prefix, iterpool)); 845251881Speter knows_something = TRUE; 846251881Speter } 847251881Speter else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0) 848251881Speter { 849251881Speter SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool)); 850251881Speter if (performed_edit) 851251881Speter knows_something = TRUE; 852251881Speter } 853251881Speter else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 || 854251881Speter strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0) 855251881Speter { 856251881Speter if (desc->kind != svn_wc_conflict_kind_text) 857251881Speter { 858251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 859251881Speter _("Invalid option; can only " 860251881Speter "resolve text conflicts with " 861251881Speter "the internal merge tool." 862251881Speter "\n\n"))); 863251881Speter continue; 864251881Speter } 865251881Speter 866251881Speter if (desc->base_abspath && desc->their_abspath && 867251881Speter desc->my_abspath && desc->merged_file) 868251881Speter { 869251881Speter svn_boolean_t remains_in_conflict; 870251881Speter 871251881Speter SVN_ERR(svn_cl__merge_file(desc->base_abspath, 872251881Speter desc->their_abspath, 873251881Speter desc->my_abspath, 874251881Speter desc->merged_file, 875251881Speter desc->local_abspath, 876251881Speter b->path_prefix, 877251881Speter b->editor_cmd, 878251881Speter b->config, 879251881Speter &remains_in_conflict, 880251881Speter iterpool)); 881251881Speter knows_something = !remains_in_conflict; 882251881Speter } 883251881Speter else 884251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 885251881Speter _("Invalid option.\n\n"))); 886251881Speter } 887251881Speter else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0) 888251881Speter { 889251881Speter /* ### This check should be earlier as it's nasty to offer an option 890251881Speter * and then when the user chooses it say 'Invalid option'. */ 891251881Speter /* ### 'merged_file' shouldn't be necessary *before* we launch the 892251881Speter * resolver: it should be the *result* of doing so. */ 893251881Speter if (desc->base_abspath && desc->their_abspath && 894251881Speter desc->my_abspath && desc->merged_file) 895251881Speter { 896251881Speter SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool)); 897251881Speter if (performed_edit) 898251881Speter knows_something = TRUE; 899251881Speter } 900251881Speter else 901251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 902251881Speter _("Invalid option.\n\n"))); 903251881Speter } 904251881Speter else if (opt->choice != -1) 905251881Speter { 906251881Speter if ((opt->choice == svn_wc_conflict_choose_mine_conflict 907251881Speter || opt->choice == svn_wc_conflict_choose_theirs_conflict) 908251881Speter && desc->is_binary) 909251881Speter { 910251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 911251881Speter _("Invalid option; cannot choose " 912251881Speter "based on conflicts in a " 913251881Speter "binary file.\n\n"))); 914251881Speter continue; 915251881Speter } 916251881Speter 917251881Speter /* We only allow the user accept the merged version of 918251881Speter the file if they've edited it, or at least looked at 919251881Speter the diff. */ 920269847Speter if (opt->choice == svn_wc_conflict_choose_merged 921251881Speter && ! knows_something) 922251881Speter { 923251881Speter SVN_ERR(svn_cmdline_fprintf( 924251881Speter stderr, iterpool, 925251881Speter _("Invalid option; use diff/edit/merge/launch " 926253734Speter "before choosing 'mark resolved'.\n\n"))); 927251881Speter continue; 928251881Speter } 929251881Speter 930251881Speter result->choice = opt->choice; 931251881Speter if (performed_edit) 932251881Speter result->save_merged = TRUE; 933251881Speter break; 934251881Speter } 935251881Speter } 936251881Speter svn_pool_destroy(iterpool); 937251881Speter 938251881Speter return SVN_NO_ERROR; 939251881Speter} 940251881Speter 941251881Speter/* Ask the user what to do about the property conflict described by DESC. 942251881Speter * Return the answer in RESULT. B is the conflict baton for this 943251881Speter * conflict resolution session. 944251881Speter * SCRATCH_POOL is used for temporary allocations. */ 945251881Speterstatic svn_error_t * 946251881Speterhandle_prop_conflict(svn_wc_conflict_result_t *result, 947251881Speter const svn_wc_conflict_description2_t *desc, 948251881Speter svn_cl__interactive_conflict_baton_t *b, 949251881Speter apr_pool_t *result_pool, 950251881Speter apr_pool_t *scratch_pool) 951251881Speter{ 952251881Speter apr_pool_t *iterpool; 953251881Speter const char *message; 954251881Speter const char *merged_file_path = NULL; 955251881Speter svn_boolean_t resolved_allowed = FALSE; 956251881Speter 957251881Speter /* ### Work around a historical bug in the provider: the path to the 958251881Speter * conflict description file was put in the 'theirs' field, and 959251881Speter * 'theirs' was put in the 'merged' field. */ 960251881Speter ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file; 961251881Speter ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL; 962251881Speter 963251881Speter SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property); 964251881Speter 965251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, 966251881Speter _("Conflict for property '%s' discovered" 967251881Speter " on '%s'.\n"), 968251881Speter desc->property_name, 969251881Speter svn_cl__local_style_skip_ancestor( 970251881Speter b->path_prefix, desc->local_abspath, 971251881Speter scratch_pool))); 972251881Speter 973251881Speter SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc, 974251881Speter scratch_pool)); 975251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message)); 976251881Speter 977251881Speter iterpool = svn_pool_create(scratch_pool); 978251881Speter while (TRUE) 979251881Speter { 980251881Speter const resolver_option_t *opt; 981251881Speter const char *options[ARRAY_LEN(prop_conflict_options)]; 982251881Speter const char **next_option = options; 983251881Speter 984251881Speter *next_option++ = "p"; 985251881Speter *next_option++ = "mf"; 986251881Speter *next_option++ = "tf"; 987251881Speter *next_option++ = "dc"; 988251881Speter *next_option++ = "e"; 989251881Speter if (resolved_allowed) 990251881Speter *next_option++ = "r"; 991251881Speter *next_option++ = "q"; 992251881Speter *next_option++ = "h"; 993251881Speter *next_option++ = NULL; 994251881Speter 995251881Speter svn_pool_clear(iterpool); 996251881Speter 997251881Speter SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb, 998251881Speter iterpool)); 999251881Speter if (! opt) 1000251881Speter continue; 1001251881Speter 1002251881Speter if (strcmp(opt->code, "q") == 0) 1003251881Speter { 1004251881Speter result->choice = opt->choice; 1005251881Speter b->accept_which = svn_cl__accept_postpone; 1006251881Speter b->quit = TRUE; 1007251881Speter break; 1008251881Speter } 1009251881Speter else if (strcmp(opt->code, "dc") == 0) 1010251881Speter { 1011251881Speter SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool)); 1012251881Speter } 1013251881Speter else if (strcmp(opt->code, "e") == 0) 1014251881Speter { 1015251881Speter SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b, 1016251881Speter result_pool, scratch_pool)); 1017251881Speter resolved_allowed = (merged_file_path != NULL); 1018251881Speter } 1019251881Speter else if (strcmp(opt->code, "r") == 0) 1020251881Speter { 1021251881Speter if (! resolved_allowed) 1022251881Speter { 1023251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, 1024251881Speter _("Invalid option; please edit the property " 1025251881Speter "first.\n\n"))); 1026251881Speter continue; 1027251881Speter } 1028251881Speter 1029251881Speter result->merged_file = merged_file_path; 1030251881Speter result->choice = svn_wc_conflict_choose_merged; 1031251881Speter break; 1032251881Speter } 1033251881Speter else if (opt->choice != -1) 1034251881Speter { 1035251881Speter result->choice = opt->choice; 1036251881Speter break; 1037251881Speter } 1038251881Speter } 1039251881Speter svn_pool_destroy(iterpool); 1040251881Speter 1041251881Speter return SVN_NO_ERROR; 1042251881Speter} 1043251881Speter 1044251881Speter/* Ask the user what to do about the tree conflict described by DESC. 1045251881Speter * Return the answer in RESULT. B is the conflict baton for this 1046251881Speter * conflict resolution session. 1047251881Speter * SCRATCH_POOL is used for temporary allocations. */ 1048251881Speterstatic svn_error_t * 1049251881Speterhandle_tree_conflict(svn_wc_conflict_result_t *result, 1050251881Speter const svn_wc_conflict_description2_t *desc, 1051251881Speter svn_cl__interactive_conflict_baton_t *b, 1052251881Speter apr_pool_t *scratch_pool) 1053251881Speter{ 1054251881Speter const char *readable_desc; 1055251881Speter apr_pool_t *iterpool; 1056251881Speter 1057251881Speter SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( 1058251881Speter &readable_desc, desc, scratch_pool)); 1059251881Speter SVN_ERR(svn_cmdline_fprintf( 1060251881Speter stderr, scratch_pool, 1061251881Speter _("Tree conflict on '%s'\n > %s\n"), 1062251881Speter svn_cl__local_style_skip_ancestor(b->path_prefix, 1063251881Speter desc->local_abspath, 1064251881Speter scratch_pool), 1065251881Speter readable_desc)); 1066251881Speter 1067251881Speter iterpool = svn_pool_create(scratch_pool); 1068251881Speter while (1) 1069251881Speter { 1070251881Speter const resolver_option_t *opt; 1071251881Speter const resolver_option_t *tc_opts; 1072251881Speter 1073251881Speter svn_pool_clear(iterpool); 1074251881Speter 1075251881Speter if (desc->operation == svn_wc_operation_update || 1076251881Speter desc->operation == svn_wc_operation_switch) 1077251881Speter { 1078251881Speter if (desc->reason == svn_wc_conflict_reason_moved_away) 1079253734Speter { 1080253734Speter if (desc->action == svn_wc_conflict_action_edit) 1081253734Speter tc_opts = tree_conflict_options_update_edit_moved_away; 1082253734Speter else 1083253734Speter tc_opts = tree_conflict_options_update_moved_away; 1084253734Speter } 1085251881Speter else if (desc->reason == svn_wc_conflict_reason_deleted) 1086251881Speter tc_opts = tree_conflict_options_update_deleted; 1087251881Speter else if (desc->reason == svn_wc_conflict_reason_replaced) 1088251881Speter tc_opts = tree_conflict_options_update_replaced; 1089251881Speter else 1090251881Speter tc_opts = tree_conflict_options; 1091251881Speter } 1092251881Speter else 1093251881Speter tc_opts = tree_conflict_options; 1094251881Speter 1095251881Speter SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool)); 1096251881Speter if (! opt) 1097251881Speter continue; 1098251881Speter 1099251881Speter if (strcmp(opt->code, "q") == 0) 1100251881Speter { 1101251881Speter result->choice = opt->choice; 1102251881Speter b->accept_which = svn_cl__accept_postpone; 1103251881Speter b->quit = TRUE; 1104251881Speter break; 1105251881Speter } 1106251881Speter else if (opt->choice != -1) 1107251881Speter { 1108251881Speter result->choice = opt->choice; 1109251881Speter break; 1110251881Speter } 1111251881Speter } 1112251881Speter svn_pool_destroy(iterpool); 1113251881Speter 1114251881Speter return SVN_NO_ERROR; 1115251881Speter} 1116251881Speter 1117251881Speter/* The body of svn_cl__conflict_func_interactive(). */ 1118251881Speterstatic svn_error_t * 1119251881Speterconflict_func_interactive(svn_wc_conflict_result_t **result, 1120251881Speter const svn_wc_conflict_description2_t *desc, 1121251881Speter void *baton, 1122251881Speter apr_pool_t *result_pool, 1123251881Speter apr_pool_t *scratch_pool) 1124251881Speter{ 1125251881Speter svn_cl__interactive_conflict_baton_t *b = baton; 1126251881Speter svn_error_t *err; 1127251881Speter 1128251881Speter /* Start out assuming we're going to postpone the conflict. */ 1129251881Speter *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, 1130251881Speter NULL, result_pool); 1131251881Speter 1132251881Speter switch (b->accept_which) 1133251881Speter { 1134251881Speter case svn_cl__accept_invalid: 1135251881Speter case svn_cl__accept_unspecified: 1136251881Speter /* No (or no valid) --accept option, fall through to prompting. */ 1137251881Speter break; 1138251881Speter case svn_cl__accept_postpone: 1139251881Speter (*result)->choice = svn_wc_conflict_choose_postpone; 1140251881Speter return SVN_NO_ERROR; 1141251881Speter case svn_cl__accept_base: 1142251881Speter (*result)->choice = svn_wc_conflict_choose_base; 1143251881Speter return SVN_NO_ERROR; 1144251881Speter case svn_cl__accept_working: 1145251881Speter /* If the caller didn't merge the property values, then I guess 1146251881Speter * 'choose working' means 'choose mine'... */ 1147251881Speter if (! desc->merged_file) 1148251881Speter (*result)->merged_file = desc->my_abspath; 1149251881Speter (*result)->choice = svn_wc_conflict_choose_merged; 1150251881Speter return SVN_NO_ERROR; 1151251881Speter case svn_cl__accept_mine_conflict: 1152251881Speter (*result)->choice = svn_wc_conflict_choose_mine_conflict; 1153251881Speter return SVN_NO_ERROR; 1154251881Speter case svn_cl__accept_theirs_conflict: 1155251881Speter (*result)->choice = svn_wc_conflict_choose_theirs_conflict; 1156251881Speter return SVN_NO_ERROR; 1157251881Speter case svn_cl__accept_mine_full: 1158251881Speter (*result)->choice = svn_wc_conflict_choose_mine_full; 1159251881Speter return SVN_NO_ERROR; 1160251881Speter case svn_cl__accept_theirs_full: 1161251881Speter (*result)->choice = svn_wc_conflict_choose_theirs_full; 1162251881Speter return SVN_NO_ERROR; 1163251881Speter case svn_cl__accept_edit: 1164251881Speter if (desc->merged_file) 1165251881Speter { 1166251881Speter if (b->external_failed) 1167251881Speter { 1168251881Speter (*result)->choice = svn_wc_conflict_choose_postpone; 1169251881Speter return SVN_NO_ERROR; 1170251881Speter } 1171251881Speter 1172251881Speter err = svn_cmdline__edit_file_externally(desc->merged_file, 1173251881Speter b->editor_cmd, b->config, 1174251881Speter scratch_pool); 1175251881Speter if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) 1176251881Speter { 1177251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1178251881Speter err->message ? err->message : 1179251881Speter _("No editor found;" 1180251881Speter " leaving all conflicts."))); 1181251881Speter svn_error_clear(err); 1182251881Speter b->external_failed = TRUE; 1183251881Speter } 1184251881Speter else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 1185251881Speter { 1186251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1187251881Speter err->message ? err->message : 1188251881Speter _("Error running editor;" 1189251881Speter " leaving all conflicts."))); 1190251881Speter svn_error_clear(err); 1191251881Speter b->external_failed = TRUE; 1192251881Speter } 1193251881Speter else if (err) 1194251881Speter return svn_error_trace(err); 1195251881Speter (*result)->choice = svn_wc_conflict_choose_merged; 1196251881Speter return SVN_NO_ERROR; 1197251881Speter } 1198251881Speter /* else, fall through to prompting. */ 1199251881Speter break; 1200251881Speter case svn_cl__accept_launch: 1201251881Speter if (desc->base_abspath && desc->their_abspath 1202251881Speter && desc->my_abspath && desc->merged_file) 1203251881Speter { 1204251881Speter svn_boolean_t remains_in_conflict; 1205251881Speter 1206251881Speter if (b->external_failed) 1207251881Speter { 1208251881Speter (*result)->choice = svn_wc_conflict_choose_postpone; 1209251881Speter return SVN_NO_ERROR; 1210251881Speter } 1211251881Speter 1212251881Speter err = svn_cl__merge_file_externally(desc->base_abspath, 1213251881Speter desc->their_abspath, 1214251881Speter desc->my_abspath, 1215251881Speter desc->merged_file, 1216251881Speter desc->local_abspath, 1217251881Speter b->config, 1218251881Speter &remains_in_conflict, 1219251881Speter scratch_pool); 1220251881Speter if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) 1221251881Speter { 1222251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1223251881Speter err->message ? err->message : 1224251881Speter _("No merge tool found;" 1225251881Speter " leaving all conflicts."))); 1226251881Speter b->external_failed = TRUE; 1227251881Speter return svn_error_trace(err); 1228251881Speter } 1229251881Speter else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) 1230251881Speter { 1231251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 1232251881Speter err->message ? err->message : 1233251881Speter _("Error running merge tool;" 1234251881Speter " leaving all conflicts."))); 1235251881Speter b->external_failed = TRUE; 1236251881Speter return svn_error_trace(err); 1237251881Speter } 1238251881Speter else if (err) 1239251881Speter return svn_error_trace(err); 1240251881Speter 1241251881Speter if (remains_in_conflict) 1242251881Speter (*result)->choice = svn_wc_conflict_choose_postpone; 1243251881Speter else 1244251881Speter (*result)->choice = svn_wc_conflict_choose_merged; 1245251881Speter return SVN_NO_ERROR; 1246251881Speter } 1247251881Speter /* else, fall through to prompting. */ 1248251881Speter break; 1249251881Speter } 1250251881Speter 1251251881Speter /* We're in interactive mode and either the user gave no --accept 1252251881Speter option or the option did not apply; let's prompt. */ 1253251881Speter 1254251881Speter /* Handle the most common cases, which is either: 1255251881Speter 1256251881Speter Conflicting edits on a file's text, or 1257251881Speter Conflicting edits on a property. 1258251881Speter */ 1259251881Speter if (((desc->kind == svn_wc_conflict_kind_text) 1260251881Speter && (desc->action == svn_wc_conflict_action_edit) 1261251881Speter && (desc->reason == svn_wc_conflict_reason_edited))) 1262251881Speter SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool)); 1263251881Speter else if (desc->kind == svn_wc_conflict_kind_property) 1264251881Speter SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool)); 1265251881Speter else if (desc->kind == svn_wc_conflict_kind_tree) 1266251881Speter SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool)); 1267251881Speter 1268251881Speter else /* other types of conflicts -- do nothing about them. */ 1269251881Speter { 1270251881Speter (*result)->choice = svn_wc_conflict_choose_postpone; 1271251881Speter } 1272251881Speter 1273251881Speter return SVN_NO_ERROR; 1274251881Speter} 1275251881Speter 1276251881Spetersvn_error_t * 1277251881Spetersvn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result, 1278251881Speter const svn_wc_conflict_description2_t *desc, 1279251881Speter void *baton, 1280251881Speter apr_pool_t *result_pool, 1281251881Speter apr_pool_t *scratch_pool) 1282251881Speter{ 1283251881Speter svn_cl__interactive_conflict_baton_t *b = baton; 1284251881Speter 1285251881Speter SVN_ERR(conflict_func_interactive(result, desc, baton, 1286251881Speter result_pool, scratch_pool)); 1287251881Speter 1288251881Speter /* If we are resolving a conflict, adjust the summary of conflicts. */ 1289251881Speter if ((*result)->choice != svn_wc_conflict_choose_postpone) 1290251881Speter { 1291251881Speter const char *local_path 1292251881Speter = svn_cl__local_style_skip_ancestor( 1293251881Speter b->path_prefix, desc->local_abspath, scratch_pool); 1294251881Speter 1295251881Speter svn_cl__conflict_stats_resolved(b->conflict_stats, local_path, 1296251881Speter desc->kind); 1297251881Speter } 1298251881Speter return SVN_NO_ERROR; 1299251881Speter} 1300