2 * conflict-callbacks.c: conflict resolution callbacks specific to the
5 * ====================================================================
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
22 * ====================================================================
25 #include <apr_xlate.h> /* for APR_LOCALE_CHARSET */
27 #define APR_WANT_STRFUNC
31 #include "svn_cmdline.h"
32 #include "svn_client.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_types.h"
35 #include "svn_pools.h"
36 #include "svn_sorts.h"
40 #include "cl-conflicts.h"
42 #include "private/svn_cmdline_private.h"
44 #include "svn_private_config.h"
46 #define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0])))
50 struct svn_cl__interactive_conflict_baton_t {
51 svn_cl__accept_t accept_which;
53 const char *editor_cmd;
54 svn_boolean_t external_failed;
55 svn_cmdline_prompt_baton_t *pb;
56 const char *path_prefix;
58 svn_cl__conflict_stats_t *conflict_stats;
62 svn_cl__get_conflict_func_interactive_baton(
63 svn_cl__interactive_conflict_baton_t **b,
64 svn_cl__accept_t accept_which,
66 const char *editor_cmd,
67 svn_cl__conflict_stats_t *conflict_stats,
68 svn_cancel_func_t cancel_func,
70 apr_pool_t *result_pool)
72 svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb));
73 pb->cancel_func = cancel_func;
74 pb->cancel_baton = cancel_baton;
76 *b = apr_palloc(result_pool, sizeof(**b));
77 (*b)->accept_which = accept_which;
78 (*b)->config = config;
79 (*b)->editor_cmd = editor_cmd;
80 (*b)->external_failed = FALSE;
82 SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool));
84 (*b)->conflict_stats = conflict_stats;
90 svn_cl__accept_from_word(const char *word)
92 /* Shorthand options are consistent with svn_cl__conflict_handler(). */
93 if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0
94 || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0)
95 return svn_cl__accept_postpone;
96 if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0)
98 return svn_cl__accept_base;
99 if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0)
101 return svn_cl__accept_working;
102 if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0
103 || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0)
104 return svn_cl__accept_mine_conflict;
105 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0
106 || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0)
107 return svn_cl__accept_theirs_conflict;
108 if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0
109 || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0)
110 return svn_cl__accept_mine_full;
111 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0
112 || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0)
113 return svn_cl__accept_theirs_full;
114 if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0
115 || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0)
116 return svn_cl__accept_edit;
117 if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0
118 || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0)
119 return svn_cl__accept_launch;
120 /* word is an invalid action. */
121 return svn_cl__accept_invalid;
125 /* Print on stdout a diff that shows incoming conflicting changes
126 * corresponding to the conflict described in DESC. */
128 show_diff(const svn_wc_conflict_description2_t *desc,
129 const char *path_prefix,
132 const char *path1, *path2;
133 const char *label1, *label2;
135 svn_stream_t *output;
136 svn_diff_file_options_t *options;
138 if (desc->merged_file)
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).
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).
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)
154 path1 = desc->my_abspath;
159 path1 = desc->their_abspath;
160 label1 = _("THEIRS");
162 path2 = desc->merged_file;
163 label2 = _("MERGED");
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;
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);
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,
187 return svn_diff_file_output_unified3(output, diff,
196 /* Print on stdout just the conflict hunks of a diff among the 'base', 'their'
197 * and 'my' files of DESC. */
199 show_conflicts(const svn_wc_conflict_description2_t *desc,
203 svn_stream_t *output;
204 svn_diff_file_options_t *options;
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,
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,
220 _("||||||| ORIGINAL"),
221 _("<<<<<<< MINE (select with 'mc')"),
222 _(">>>>>>> THEIRS (select with 'tc')"),
224 svn_diff_conflict_display_only_conflicts,
228 /* Perform a 3-way merge of the conflicting values of a property,
229 * and write the result to the OUTPUT stream.
231 * If MERGED_ABSPATH is non-NULL, use it as 'my' version instead of
234 * Assume the values are printable UTF-8 text.
237 merge_prop_conflict(svn_stream_t *output,
238 const svn_wc_conflict_description2_t *desc,
239 const char *merged_abspath,
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);
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)
252 const char *empty_file;
254 SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file,
255 NULL, svn_io_file_del_on_pool_cleanup,
258 base_abspath = empty_file;
260 my_abspath = empty_file;
262 their_abspath = empty_file;
265 options->ignore_eol_style = TRUE;
266 SVN_ERR(svn_diff_file_diff3_2(&diff,
268 merged_abspath ? merged_abspath : my_abspath,
271 SVN_ERR(svn_diff_file_output_merge2(output, diff,
273 merged_abspath ? merged_abspath
276 _("||||||| ORIGINAL"),
280 svn_diff_conflict_display_modified_original_latest,
286 /* Display the conflicting values of a property as a 3-way diff.
288 * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of
291 * Assume the values are printable UTF-8 text.
294 show_prop_conflict(const svn_wc_conflict_description2_t *desc,
295 const char *merged_abspath,
298 svn_stream_t *output;
300 SVN_ERR(svn_stream_for_stdout(&output, pool));
301 SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, pool));
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.
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. */
316 open_editor(svn_boolean_t *performed_edit,
317 const char *merged_file,
318 svn_cl__interactive_conflict_baton_t *b,
325 err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd,
327 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
329 svn_error_t *root_err = svn_error_root_cause(err);
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);
336 else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
338 svn_error_t *root_err = svn_error_root_cause(err);
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);
346 return svn_error_trace(err);
348 *performed_edit = TRUE;
351 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
352 _("Invalid option; there's no "
353 "merged version to edit.\n\n")));
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. */
362 edit_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)
369 const char *file_path;
370 svn_boolean_t performed_edit = FALSE;
371 svn_stream_t *merged_prop;
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 */,
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);
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.
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. */
396 launch_resolver(svn_boolean_t *performed_edit,
397 const svn_wc_conflict_description2_t *desc,
398 svn_cl__interactive_conflict_baton_t *b,
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,
407 if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
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);
415 else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
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);
424 return svn_error_trace(err);
425 else if (performed_edit)
426 *performed_edit = TRUE;
432 /* Maximum line length for the prompt string. */
433 #define MAX_PROMPT_WIDTH 70
435 /* Description of a resolver option */
436 typedef struct resolver_option_t
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 */
444 /* Resolver options for a text conflict */
445 /* (opt->code == "" causes a blank line break in help_string()) */
446 static const resolver_option_t text_conflict_options[] =
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
451 { "e", N_("edit file"), N_("change merged file in an editor"
454 { "df", N_("show diff"), N_("show all changes made to merged file"),
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 "
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 "
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"
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 },
489 /* Resolver options for a property conflict */
490 static const resolver_option_t prop_conflict_options[] =
492 { "mf", N_("my version"), N_("accept my version of entire file (even "
493 "non-conflicts) [mine-full]"),
494 svn_wc_conflict_choose_mine_full },
495 { "tf", N_("their version"), N_("accept their version of entire file "
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"
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"
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 },
512 /* Resolver options for an obstructued addition */
513 static const resolver_option_t obstructed_add_options[] =
515 { "mf", N_("my version"), N_("accept pre-existing item (ignore "
516 "upstream addition) [mine-full]"),
517 svn_wc_conflict_choose_mine_full },
518 { "tf", N_("their version"), N_("accept incoming item (overwrite "
519 "pre-existing item) [theirs-full]"),
520 svn_wc_conflict_choose_theirs_full },
521 { "p", N_("postpone"), N_("mark the conflict to be resolved later"
523 svn_wc_conflict_choose_postpone },
524 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
525 svn_wc_conflict_choose_postpone },
526 { "h", N_("help"), N_("show this help (also '?')"), -1 },
530 /* Resolver options for a tree conflict */
531 static const resolver_option_t tree_conflict_options[] =
533 { "r", N_("mark resolved"), N_("accept current working copy state"),
534 svn_wc_conflict_choose_merged },
535 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
536 svn_wc_conflict_choose_postpone },
537 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
538 svn_wc_conflict_choose_postpone },
539 { "h", N_("help"), N_("show this help (also '?')"), -1 },
543 static const resolver_option_t tree_conflict_options_update_moved_away[] =
545 { "mc", N_("apply update (recommended)"),
546 N_("apply update to the move destination"
548 svn_wc_conflict_choose_mine_conflict },
549 { "r", N_("discard update (breaks move)"), N_("discard update, mark "
550 "resolved, the move will "
551 "will become a copy"),
552 svn_wc_conflict_choose_merged },
553 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
554 svn_wc_conflict_choose_postpone },
555 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
556 svn_wc_conflict_choose_postpone },
557 { "h", N_("help"), N_("show this help (also '?')"), -1 },
561 static const resolver_option_t tree_conflict_options_update_edit_moved_away[] =
563 { "mc", N_("apply update to move destination"),
564 N_("apply incoming update to move destination"
566 svn_wc_conflict_choose_mine_conflict },
567 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
568 svn_wc_conflict_choose_postpone },
569 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
570 svn_wc_conflict_choose_postpone },
571 { "h", N_("help"), N_("show this help (also '?')"), -1 },
575 static const resolver_option_t tree_conflict_options_update_deleted[] =
577 { "mc", N_("keep affected local moves"), N_("keep any local moves affected "
578 "by this deletion [mine-conflict]"),
579 svn_wc_conflict_choose_mine_conflict },
580 { "r", N_("mark resolved (breaks moves)"), N_("mark resolved, any affected "
581 "moves will become copies"),
582 svn_wc_conflict_choose_merged },
583 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
584 svn_wc_conflict_choose_postpone },
585 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
586 svn_wc_conflict_choose_postpone },
587 { "h", N_("help"), N_("show this help (also '?')"), -1 },
591 static const resolver_option_t tree_conflict_options_update_replaced[] =
593 { "mc", N_("keep affected local moves"), N_("keep any moves affected by this "
594 "replacement [mine-conflict]"),
595 svn_wc_conflict_choose_mine_conflict },
596 { "r", N_("mark resolved (breaks moves)"), N_("mark resolved (any affected "
597 "moves will become copies)"),
598 svn_wc_conflict_choose_merged },
599 { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
600 svn_wc_conflict_choose_postpone },
601 { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
602 svn_wc_conflict_choose_postpone },
603 { "h", N_("help"), N_("show this help (also '?')"), -1 },
608 /* Return a pointer to the option description in OPTIONS matching the
609 * one- or two-character OPTION_CODE. Return NULL if not found. */
610 static const resolver_option_t *
611 find_option(const resolver_option_t *options,
612 const char *option_code)
614 const resolver_option_t *opt;
616 for (opt = options; opt->code; opt++)
618 /* Ignore code "" (blank lines) which is not a valid answer. */
619 if (opt->code[0] && strcmp(opt->code, option_code) == 0)
625 /* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
626 * non-null, select only the options whose codes are mentioned in it. */
628 prompt_string(const resolver_option_t *options,
629 const char *const *option_codes,
632 const char *result = _("Select:");
633 int left_margin = svn_utf_cstring_utf8_width(result);
634 const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
635 int this_line_len = left_margin;
636 svn_boolean_t first = TRUE;
640 const resolver_option_t *opt;
648 opt = find_option(options, *option_codes++);
658 result = apr_pstrcat(pool, result, ",", (char *)NULL);
659 s = apr_psprintf(pool, _(" (%s) %s"),
660 opt->code, _(opt->short_desc));
661 slen = svn_utf_cstring_utf8_width(s);
662 /* Break the line if adding the next option would make it too long */
663 if (this_line_len + slen > MAX_PROMPT_WIDTH)
665 result = apr_pstrcat(pool, result, line_sep, (char *)NULL);
666 this_line_len = left_margin;
668 result = apr_pstrcat(pool, result, s, (char *)NULL);
669 this_line_len += slen;
672 return apr_pstrcat(pool, result, ": ", (char *)NULL);
675 /* Return a help string listing the OPTIONS. */
677 help_string(const resolver_option_t *options,
680 const char *result = "";
681 const resolver_option_t *opt;
683 for (opt = options; opt->code; opt++)
685 /* Append a line describing OPT, or a blank line if its code is "". */
688 const char *s = apr_psprintf(pool, " (%s)", opt->code);
690 result = apr_psprintf(pool, "%s%-6s - %s\n",
691 result, s, _(opt->long_desc));
695 result = apr_pstrcat(pool, result, "\n", (char *)NULL);
698 result = apr_pstrcat(pool, result,
699 _("Words in square brackets are the corresponding "
700 "--accept option arguments.\n"),
705 /* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
706 * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen
707 * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
708 * NULL if the answer was not one of them.
710 * If the answer is the (globally recognized) 'help' option, then display
711 * the help (on stderr) and return with *OPT == NULL.
714 prompt_user(const resolver_option_t **opt,
715 const resolver_option_t *conflict_options,
716 const char *const *options_to_show,
718 apr_pool_t *scratch_pool)
721 = prompt_string(conflict_options, options_to_show, scratch_pool);
724 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
725 if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
727 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
728 help_string(conflict_options,
734 *opt = find_option(conflict_options, answer);
737 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
738 _("Unrecognized option.\n\n")));
744 /* Ask the user what to do about the text conflict described by DESC.
745 * Return the answer in RESULT. B is the conflict baton for this
746 * conflict resolution session.
747 * SCRATCH_POOL is used for temporary allocations. */
749 handle_text_conflict(svn_wc_conflict_result_t *result,
750 const svn_wc_conflict_description2_t *desc,
751 svn_cl__interactive_conflict_baton_t *b,
752 apr_pool_t *scratch_pool)
754 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
755 svn_boolean_t diff_allowed = FALSE;
756 /* Have they done something that might have affected the merged
757 file (so that we need to save a .edited copy)? */
758 svn_boolean_t performed_edit = FALSE;
759 /* Have they done *something* (edit, look at diff, etc) to
760 give them a rational basis for choosing (r)esolved? */
761 svn_boolean_t knows_something = FALSE;
763 SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text);
765 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
766 _("Conflict discovered in file '%s'.\n"),
767 svn_cl__local_style_skip_ancestor(
768 b->path_prefix, desc->local_abspath,
771 /* Diffing can happen between base and merged, to show conflict
772 markers to the user (this is the typical 3-way merge
773 scenario), or if no base is available, we can show a diff
774 between mine and theirs. */
775 if ((desc->merged_file && desc->base_abspath)
776 || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
781 const char *options[ARRAY_LEN(text_conflict_options)];
782 const char **next_option = options;
783 const resolver_option_t *opt;
785 svn_pool_clear(iterpool);
787 *next_option++ = "p";
790 *next_option++ = "df";
791 *next_option++ = "e";
792 *next_option++ = "m";
795 *next_option++ = "r";
797 if (! desc->is_binary)
799 *next_option++ = "mc";
800 *next_option++ = "tc";
806 *next_option++ = "r";
807 *next_option++ = "mf";
808 *next_option++ = "tf";
810 *next_option++ = "s";
811 *next_option++ = NULL;
813 SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->pb,
818 if (strcmp(opt->code, "q") == 0)
820 result->choice = opt->choice;
821 b->accept_which = svn_cl__accept_postpone;
825 else if (strcmp(opt->code, "s") == 0)
827 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
828 help_string(text_conflict_options,
831 else if (strcmp(opt->code, "dc") == 0)
835 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
836 _("Invalid option; cannot "
837 "display conflicts for a "
838 "binary file.\n\n")));
841 else if (! (desc->my_abspath && desc->base_abspath &&
842 desc->their_abspath))
844 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
845 _("Invalid option; original "
846 "files not available.\n\n")));
849 SVN_ERR(show_conflicts(desc, iterpool));
850 knows_something = TRUE;
852 else if (strcmp(opt->code, "df") == 0)
856 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
857 _("Invalid option; there's no "
858 "merged version to diff.\n\n")));
862 SVN_ERR(show_diff(desc, b->path_prefix, iterpool));
863 knows_something = TRUE;
865 else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
867 SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool));
869 knows_something = TRUE;
871 else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
872 strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
874 if (desc->kind != svn_wc_conflict_kind_text)
876 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
877 _("Invalid option; can only "
878 "resolve text conflicts with "
879 "the internal merge tool."
884 if (desc->base_abspath && desc->their_abspath &&
885 desc->my_abspath && desc->merged_file)
887 svn_boolean_t remains_in_conflict;
889 SVN_ERR(svn_cl__merge_file(desc->base_abspath,
897 &remains_in_conflict,
899 knows_something = !remains_in_conflict;
902 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
903 _("Invalid option.\n\n")));
905 else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
907 /* ### This check should be earlier as it's nasty to offer an option
908 * and then when the user chooses it say 'Invalid option'. */
909 /* ### 'merged_file' shouldn't be necessary *before* we launch the
910 * resolver: it should be the *result* of doing so. */
911 if (desc->base_abspath && desc->their_abspath &&
912 desc->my_abspath && desc->merged_file)
914 SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool));
916 knows_something = TRUE;
919 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
920 _("Invalid option.\n\n")));
922 else if (opt->choice != -1)
924 if ((opt->choice == svn_wc_conflict_choose_mine_conflict
925 || opt->choice == svn_wc_conflict_choose_theirs_conflict)
928 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
929 _("Invalid option; cannot choose "
930 "based on conflicts in a "
931 "binary file.\n\n")));
935 /* We only allow the user accept the merged version of
936 the file if they've edited it, or at least looked at
938 if (result->choice == svn_wc_conflict_choose_merged
939 && ! knows_something)
941 SVN_ERR(svn_cmdline_fprintf(
943 _("Invalid option; use diff/edit/merge/launch "
944 "before choosing 'mark resolved'.\n\n")));
948 result->choice = opt->choice;
950 result->save_merged = TRUE;
954 svn_pool_destroy(iterpool);
959 /* Ask the user what to do about the property conflict described by DESC.
960 * Return the answer in RESULT. B is the conflict baton for this
961 * conflict resolution session.
962 * SCRATCH_POOL is used for temporary allocations. */
964 handle_prop_conflict(svn_wc_conflict_result_t *result,
965 const svn_wc_conflict_description2_t *desc,
966 svn_cl__interactive_conflict_baton_t *b,
967 apr_pool_t *result_pool,
968 apr_pool_t *scratch_pool)
970 apr_pool_t *iterpool;
972 const char *merged_file_path = NULL;
973 svn_boolean_t resolved_allowed = FALSE;
975 /* ### Work around a historical bug in the provider: the path to the
976 * conflict description file was put in the 'theirs' field, and
977 * 'theirs' was put in the 'merged' field. */
978 ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file;
979 ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL;
981 SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property);
983 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
984 _("Conflict for property '%s' discovered"
987 svn_cl__local_style_skip_ancestor(
988 b->path_prefix, desc->local_abspath,
991 SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc,
993 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message));
995 iterpool = svn_pool_create(scratch_pool);
998 const resolver_option_t *opt;
999 const char *options[ARRAY_LEN(prop_conflict_options)];
1000 const char **next_option = options;
1002 *next_option++ = "p";
1003 *next_option++ = "mf";
1004 *next_option++ = "tf";
1005 *next_option++ = "dc";
1006 *next_option++ = "e";
1007 if (resolved_allowed)
1008 *next_option++ = "r";
1009 *next_option++ = "q";
1010 *next_option++ = "h";
1011 *next_option++ = NULL;
1013 svn_pool_clear(iterpool);
1015 SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb,
1020 if (strcmp(opt->code, "q") == 0)
1022 result->choice = opt->choice;
1023 b->accept_which = svn_cl__accept_postpone;
1027 else if (strcmp(opt->code, "dc") == 0)
1029 SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool));
1031 else if (strcmp(opt->code, "e") == 0)
1033 SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b,
1034 result_pool, scratch_pool));
1035 resolved_allowed = (merged_file_path != NULL);
1037 else if (strcmp(opt->code, "r") == 0)
1039 if (! resolved_allowed)
1041 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1042 _("Invalid option; please edit the property "
1047 result->merged_file = merged_file_path;
1048 result->choice = svn_wc_conflict_choose_merged;
1051 else if (opt->choice != -1)
1053 result->choice = opt->choice;
1057 svn_pool_destroy(iterpool);
1059 return SVN_NO_ERROR;
1062 /* Ask the user what to do about the tree conflict described by DESC.
1063 * Return the answer in RESULT. B is the conflict baton for this
1064 * conflict resolution session.
1065 * SCRATCH_POOL is used for temporary allocations. */
1066 static svn_error_t *
1067 handle_tree_conflict(svn_wc_conflict_result_t *result,
1068 const svn_wc_conflict_description2_t *desc,
1069 svn_cl__interactive_conflict_baton_t *b,
1070 apr_pool_t *scratch_pool)
1072 const char *readable_desc;
1073 apr_pool_t *iterpool;
1075 SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
1076 &readable_desc, desc, scratch_pool));
1077 SVN_ERR(svn_cmdline_fprintf(
1078 stderr, scratch_pool,
1079 _("Tree conflict on '%s'\n > %s\n"),
1080 svn_cl__local_style_skip_ancestor(b->path_prefix,
1081 desc->local_abspath,
1085 iterpool = svn_pool_create(scratch_pool);
1088 const resolver_option_t *opt;
1089 const resolver_option_t *tc_opts;
1091 svn_pool_clear(iterpool);
1093 if (desc->operation == svn_wc_operation_update ||
1094 desc->operation == svn_wc_operation_switch)
1096 if (desc->reason == svn_wc_conflict_reason_moved_away)
1098 if (desc->action == svn_wc_conflict_action_edit)
1099 tc_opts = tree_conflict_options_update_edit_moved_away;
1101 tc_opts = tree_conflict_options_update_moved_away;
1103 else if (desc->reason == svn_wc_conflict_reason_deleted)
1104 tc_opts = tree_conflict_options_update_deleted;
1105 else if (desc->reason == svn_wc_conflict_reason_replaced)
1106 tc_opts = tree_conflict_options_update_replaced;
1108 tc_opts = tree_conflict_options;
1111 tc_opts = tree_conflict_options;
1113 SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool));
1117 if (strcmp(opt->code, "q") == 0)
1119 result->choice = opt->choice;
1120 b->accept_which = svn_cl__accept_postpone;
1124 else if (opt->choice != -1)
1126 result->choice = opt->choice;
1130 svn_pool_destroy(iterpool);
1132 return SVN_NO_ERROR;
1135 /* Ask the user what to do about the obstructed add described by DESC.
1136 * Return the answer in RESULT. B is the conflict baton for this
1137 * conflict resolution session.
1138 * SCRATCH_POOL is used for temporary allocations. */
1139 static svn_error_t *
1140 handle_obstructed_add(svn_wc_conflict_result_t *result,
1141 const svn_wc_conflict_description2_t *desc,
1142 svn_cl__interactive_conflict_baton_t *b,
1143 apr_pool_t *scratch_pool)
1145 apr_pool_t *iterpool;
1147 SVN_ERR(svn_cmdline_fprintf(
1148 stderr, scratch_pool,
1149 _("Conflict discovered when trying to add '%s'.\n"
1150 "An object of the same name already exists.\n"),
1151 svn_cl__local_style_skip_ancestor(b->path_prefix,
1152 desc->local_abspath,
1155 iterpool = svn_pool_create(scratch_pool);
1158 const resolver_option_t *opt;
1160 svn_pool_clear(iterpool);
1162 SVN_ERR(prompt_user(&opt, obstructed_add_options, NULL, b->pb,
1167 if (strcmp(opt->code, "q") == 0)
1169 result->choice = opt->choice;
1170 b->accept_which = svn_cl__accept_postpone;
1174 else if (opt->choice != -1)
1176 result->choice = opt->choice;
1180 svn_pool_destroy(iterpool);
1182 return SVN_NO_ERROR;
1185 /* The body of svn_cl__conflict_func_interactive(). */
1186 static svn_error_t *
1187 conflict_func_interactive(svn_wc_conflict_result_t **result,
1188 const svn_wc_conflict_description2_t *desc,
1190 apr_pool_t *result_pool,
1191 apr_pool_t *scratch_pool)
1193 svn_cl__interactive_conflict_baton_t *b = baton;
1196 /* Start out assuming we're going to postpone the conflict. */
1197 *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
1200 switch (b->accept_which)
1202 case svn_cl__accept_invalid:
1203 case svn_cl__accept_unspecified:
1204 /* No (or no valid) --accept option, fall through to prompting. */
1206 case svn_cl__accept_postpone:
1207 (*result)->choice = svn_wc_conflict_choose_postpone;
1208 return SVN_NO_ERROR;
1209 case svn_cl__accept_base:
1210 (*result)->choice = svn_wc_conflict_choose_base;
1211 return SVN_NO_ERROR;
1212 case svn_cl__accept_working:
1213 /* If the caller didn't merge the property values, then I guess
1214 * 'choose working' means 'choose mine'... */
1215 if (! desc->merged_file)
1216 (*result)->merged_file = desc->my_abspath;
1217 (*result)->choice = svn_wc_conflict_choose_merged;
1218 return SVN_NO_ERROR;
1219 case svn_cl__accept_mine_conflict:
1220 (*result)->choice = svn_wc_conflict_choose_mine_conflict;
1221 return SVN_NO_ERROR;
1222 case svn_cl__accept_theirs_conflict:
1223 (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
1224 return SVN_NO_ERROR;
1225 case svn_cl__accept_mine_full:
1226 (*result)->choice = svn_wc_conflict_choose_mine_full;
1227 return SVN_NO_ERROR;
1228 case svn_cl__accept_theirs_full:
1229 (*result)->choice = svn_wc_conflict_choose_theirs_full;
1230 return SVN_NO_ERROR;
1231 case svn_cl__accept_edit:
1232 if (desc->merged_file)
1234 if (b->external_failed)
1236 (*result)->choice = svn_wc_conflict_choose_postpone;
1237 return SVN_NO_ERROR;
1240 err = svn_cmdline__edit_file_externally(desc->merged_file,
1241 b->editor_cmd, b->config,
1243 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
1245 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1246 err->message ? err->message :
1247 _("No editor found;"
1248 " leaving all conflicts.")));
1249 svn_error_clear(err);
1250 b->external_failed = TRUE;
1252 else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
1254 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1255 err->message ? err->message :
1256 _("Error running editor;"
1257 " leaving all conflicts.")));
1258 svn_error_clear(err);
1259 b->external_failed = TRUE;
1262 return svn_error_trace(err);
1263 (*result)->choice = svn_wc_conflict_choose_merged;
1264 return SVN_NO_ERROR;
1266 /* else, fall through to prompting. */
1268 case svn_cl__accept_launch:
1269 if (desc->base_abspath && desc->their_abspath
1270 && desc->my_abspath && desc->merged_file)
1272 svn_boolean_t remains_in_conflict;
1274 if (b->external_failed)
1276 (*result)->choice = svn_wc_conflict_choose_postpone;
1277 return SVN_NO_ERROR;
1280 err = svn_cl__merge_file_externally(desc->base_abspath,
1281 desc->their_abspath,
1284 desc->local_abspath,
1286 &remains_in_conflict,
1288 if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
1290 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1291 err->message ? err->message :
1292 _("No merge tool found;"
1293 " leaving all conflicts.")));
1294 b->external_failed = TRUE;
1295 return svn_error_trace(err);
1297 else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
1299 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1300 err->message ? err->message :
1301 _("Error running merge tool;"
1302 " leaving all conflicts.")));
1303 b->external_failed = TRUE;
1304 return svn_error_trace(err);
1307 return svn_error_trace(err);
1309 if (remains_in_conflict)
1310 (*result)->choice = svn_wc_conflict_choose_postpone;
1312 (*result)->choice = svn_wc_conflict_choose_merged;
1313 return SVN_NO_ERROR;
1315 /* else, fall through to prompting. */
1319 /* We're in interactive mode and either the user gave no --accept
1320 option or the option did not apply; let's prompt. */
1322 /* Handle the most common cases, which is either:
1324 Conflicting edits on a file's text, or
1325 Conflicting edits on a property.
1327 if (((desc->kind == svn_wc_conflict_kind_text)
1328 && (desc->action == svn_wc_conflict_action_edit)
1329 && (desc->reason == svn_wc_conflict_reason_edited)))
1330 SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool));
1331 else if (desc->kind == svn_wc_conflict_kind_property)
1332 SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool));
1335 Dealing with obstruction of additions can be tricky. The
1336 obstructing item could be unversioned, versioned, or even
1337 schedule-add. Here's a matrix of how the caller should behave,
1338 based on results we return.
1340 Unversioned Versioned Schedule-Add
1342 choose_mine skip addition, skip addition skip addition
1345 choose_theirs destroy file, schedule-delete, revert add,
1346 add new item. add new item. rm file,
1349 postpone [ bail out ]
1352 else if ((desc->action == svn_wc_conflict_action_add)
1353 && (desc->reason == svn_wc_conflict_reason_obstructed))
1354 SVN_ERR(handle_obstructed_add(*result, desc, b, scratch_pool));
1356 else if (desc->kind == svn_wc_conflict_kind_tree)
1357 SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool));
1359 else /* other types of conflicts -- do nothing about them. */
1361 (*result)->choice = svn_wc_conflict_choose_postpone;
1364 return SVN_NO_ERROR;
1368 svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
1369 const svn_wc_conflict_description2_t *desc,
1371 apr_pool_t *result_pool,
1372 apr_pool_t *scratch_pool)
1374 svn_cl__interactive_conflict_baton_t *b = baton;
1376 SVN_ERR(conflict_func_interactive(result, desc, baton,
1377 result_pool, scratch_pool));
1379 /* If we are resolving a conflict, adjust the summary of conflicts. */
1380 if ((*result)->choice != svn_wc_conflict_choose_postpone)
1382 const char *local_path
1383 = svn_cl__local_style_skip_ancestor(
1384 b->path_prefix, desc->local_abspath, scratch_pool);
1386 svn_cl__conflict_stats_resolved(b->conflict_stats, local_path,
1389 return SVN_NO_ERROR;