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])))
51 svn_cl__accept_from_word(const char *word)
53 /* Shorthand options are consistent with svn_cl__conflict_handler(). */
54 if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0
55 || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0)
56 return svn_cl__accept_postpone;
57 if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0)
59 return svn_cl__accept_base;
60 if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0)
62 return svn_cl__accept_working;
63 if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0
64 || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0)
65 return svn_cl__accept_mine_conflict;
66 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0
67 || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0)
68 return svn_cl__accept_theirs_conflict;
69 if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0
70 || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0)
71 return svn_cl__accept_mine_full;
72 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0
73 || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0)
74 return svn_cl__accept_theirs_full;
75 if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0
76 || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0)
77 return svn_cl__accept_edit;
78 if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0
79 || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0)
80 return svn_cl__accept_launch;
81 if (strcmp(word, SVN_CL__ACCEPT_RECOMMENDED) == 0
82 || strcmp(word, "r") == 0)
83 return svn_cl__accept_recommended;
84 /* word is an invalid action. */
85 return svn_cl__accept_invalid;
89 /* Print on stdout a diff that shows incoming conflicting changes
90 * corresponding to the conflict described in CONFLICT. */
92 show_diff(svn_client_conflict_t *conflict,
93 const char *merged_abspath,
94 const char *path_prefix,
95 svn_cancel_func_t cancel_func,
99 const char *path1, *path2;
100 const char *label1, *label2;
102 svn_stream_t *output;
103 svn_diff_file_options_t *options;
104 const char *my_abspath;
105 const char *their_abspath;
107 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, NULL,
109 conflict, pool, pool));
112 /* For conflicts recorded by the 'merge' operation, show a diff between
113 * 'mine' (the working version of the file as it appeared before the
114 * 'merge' operation was run) and 'merged' (the version of the file
115 * as it appears after the merge operation).
117 * For conflicts recorded by the 'update' and 'switch' operations,
118 * show a diff between 'theirs' (the new pristine version of the
119 * file) and 'merged' (the version of the file as it appears with
120 * local changes merged with the new pristine version).
122 * This way, the diff is always minimal and clearly identifies changes
123 * brought into the working copy by the update/switch/merge operation. */
124 if (svn_client_conflict_get_operation(conflict) == svn_wc_operation_merge)
131 path1 = their_abspath;
132 label1 = _("THEIRS");
134 path2 = merged_abspath;
135 label2 = _("MERGED");
139 /* There's no merged file, but we can show the
140 difference between mine and theirs. */
141 path1 = their_abspath;
142 label1 = _("THEIRS");
147 label1 = apr_psprintf(pool, "%s\t- %s",
148 svn_cl__local_style_skip_ancestor(
149 path_prefix, path1, pool), label1);
150 label2 = apr_psprintf(pool, "%s\t- %s",
151 svn_cl__local_style_skip_ancestor(
152 path_prefix, path2, pool), label2);
154 options = svn_diff_file_options_create(pool);
155 options->ignore_eol_style = TRUE;
156 SVN_ERR(svn_stream_for_stdout(&output, pool));
157 SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2,
159 return svn_diff_file_output_unified4(output, diff,
164 options->show_c_function,
165 options->context_size,
166 cancel_func, cancel_baton,
171 /* Print on stdout just the conflict hunks of a diff among the 'base', 'their'
172 * and 'my' files of CONFLICT. */
174 show_conflicts(svn_client_conflict_t *conflict,
175 svn_cancel_func_t cancel_func,
180 svn_stream_t *output;
181 svn_diff_file_options_t *options;
182 const char *base_abspath;
183 const char *my_abspath;
184 const char *their_abspath;
186 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
187 &base_abspath, &their_abspath,
188 conflict, pool, pool));
189 options = svn_diff_file_options_create(pool);
190 options->ignore_eol_style = TRUE;
191 SVN_ERR(svn_stream_for_stdout(&output, pool));
192 SVN_ERR(svn_diff_file_diff3_2(&diff, base_abspath, my_abspath, their_abspath,
194 /* ### Consider putting the markers/labels from
195 ### svn_wc__merge_internal in the conflict description. */
196 return svn_diff_file_output_merge3(
197 output, diff, base_abspath, my_abspath, their_abspath,
198 _("||||||| ORIGINAL"),
199 _("<<<<<<< MINE (select with 'mc')"),
200 _(">>>>>>> THEIRS (select with 'tc')"),
202 svn_diff_conflict_display_only_conflicts,
208 /* Perform a 3-way merge of the conflicting values of a property,
209 * and write the result to the OUTPUT stream.
211 * If MERGED_PROPVAL is non-NULL, use it as 'my' version instead of
214 * Assume the values are printable UTF-8 text.
217 merge_prop_conflict(svn_stream_t *output,
218 const svn_string_t *base_propval,
219 const svn_string_t *my_propval,
220 const svn_string_t *their_propval,
221 const svn_string_t *merged_propval,
222 svn_cancel_func_t cancel_func,
226 svn_diff_file_options_t *options = svn_diff_file_options_create(pool);
229 /* If any of the property values is missing, use an empty value instead
230 * for the purpose of showing a diff. */
231 if (base_propval == NULL)
232 base_propval = svn_string_create_empty(pool);
233 if (my_propval == NULL)
234 my_propval = svn_string_create_empty(pool);
235 if (their_propval == NULL)
236 their_propval = svn_string_create_empty(pool);
238 options->ignore_eol_style = TRUE;
239 SVN_ERR(svn_diff_mem_string_diff3(&diff, base_propval,
241 merged_propval : my_propval,
242 their_propval, options, pool));
243 SVN_ERR(svn_diff_mem_string_output_merge3(
244 output, diff, base_propval,
245 merged_propval ? merged_propval : my_propval, their_propval,
246 _("||||||| ORIGINAL"),
250 svn_diff_conflict_display_modified_original_latest,
258 /* Display the conflicting values of a property as a 3-way diff.
260 * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of
263 * Assume the values are printable UTF-8 text.
266 show_prop_conflict(const svn_string_t *base_propval,
267 const svn_string_t *my_propval,
268 const svn_string_t *their_propval,
269 const svn_string_t *merged_propval,
270 svn_cancel_func_t cancel_func,
274 svn_stream_t *output;
276 SVN_ERR(svn_stream_for_stdout(&output, pool));
277 SVN_ERR(merge_prop_conflict(output, base_propval, my_propval, their_propval,
278 merged_propval, cancel_func, cancel_baton, pool));
283 /* Run an external editor, passing it the MERGED_ABSPATH, or, if the
284 * 'merged' file is null, return an error. The tool to use is determined by
285 * B->editor_cmd, B->config and environment variables; see
286 * svn_cl__edit_file_externally() for details.
288 * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
289 * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
290 * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
291 * return that error. */
293 open_editor(svn_boolean_t *performed_edit,
294 const char *merged_abspath,
295 const char *editor_cmd,
303 err = svn_cmdline__edit_file_externally(merged_abspath, editor_cmd,
305 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR ||
306 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
311 message = svn_err_best_message(err, buf, sizeof(buf));
312 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", message));
313 svn_error_clear(err);
316 return svn_error_trace(err);
318 *performed_edit = TRUE;
321 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
322 _("Invalid option; there's no "
323 "merged version to edit.\n\n")));
328 /* Run an external editor on the merged property value with conflict markers.
329 * Return the edited result in *MERGED_PROPVAL.
330 * If the edit is aborted, set *MERGED_ABSPATH and *MERGED_PROPVAL to NULL.
331 * The tool to use is determined by B->editor_cmd, B->config and
332 * environment variables; see svn_cl__edit_file_externally() for details. */
334 edit_prop_conflict(const svn_string_t **merged_propval,
335 const svn_string_t *base_propval,
336 const svn_string_t *my_propval,
337 const svn_string_t *their_propval,
338 const char *editor_cmd,
340 svn_cmdline_prompt_baton_t *pb,
341 apr_pool_t *result_pool,
342 apr_pool_t *scratch_pool)
344 const char *file_path;
345 svn_boolean_t performed_edit = FALSE;
346 svn_stream_t *merged_prop;
348 SVN_ERR(svn_stream_open_unique(&merged_prop, &file_path, NULL,
349 svn_io_file_del_on_pool_cleanup,
350 scratch_pool, scratch_pool));
351 SVN_ERR(merge_prop_conflict(merged_prop, base_propval, my_propval,
356 SVN_ERR(svn_stream_close(merged_prop));
357 SVN_ERR(open_editor(&performed_edit, file_path, editor_cmd,
358 config, scratch_pool));
359 if (performed_edit && merged_propval)
361 svn_stringbuf_t *buf;
363 SVN_ERR(svn_stringbuf_from_file2(&buf, file_path, scratch_pool));
364 *merged_propval = svn_string_create_from_buf(buf, result_pool);
370 /* Maximum line length for the prompt string. */
371 #define MAX_PROMPT_WIDTH 70
373 /* Description of a resolver option.
374 * Resolver options are used to build the resolver's conflict prompt.
375 * The user types a code to select the corresponding conflict resolution option.
376 * Some resolver options have a corresponding --accept argument. */
377 typedef struct resolver_option_t
379 const char *code; /* one or two characters */
380 svn_client_conflict_option_id_t choice;
381 /* or ..._undefined if not from libsvn_client */
382 const char *accept_arg; /* --accept option argument (NOT localized) */
385 typedef struct client_option_t
387 const char *code; /* one or two characters */
388 const char *label; /* label in prompt (localized) */
389 const char *long_desc; /* longer description (localized) */
390 svn_client_conflict_option_id_t choice;
391 /* or ..._undefined if not from libsvn_client */
392 const char *accept_arg; /* --accept option argument (NOT localized) */
393 svn_boolean_t is_recommended; /* if TRUE, try this option before prompting */
396 /* Resolver options for conflict options offered by libsvn_client. */
397 static const resolver_option_t builtin_resolver_options[] =
399 { "r", svn_client_conflict_option_merged_text,
400 SVN_CL__ACCEPT_WORKING },
401 { "mc", svn_client_conflict_option_working_text_where_conflicted,
402 SVN_CL__ACCEPT_MINE_CONFLICT },
403 { "tc", svn_client_conflict_option_incoming_text_where_conflicted,
404 SVN_CL__ACCEPT_THEIRS_CONFLICT },
405 { "mf", svn_client_conflict_option_working_text,
406 SVN_CL__ACCEPT_MINE_FULL},
407 { "tf", svn_client_conflict_option_incoming_text,
408 SVN_CL__ACCEPT_THEIRS_FULL },
409 { "p", svn_client_conflict_option_postpone,
410 SVN_CL__ACCEPT_POSTPONE },
412 /* This option resolves a tree conflict to the current working copy state. */
413 { "r", svn_client_conflict_option_accept_current_wc_state,
414 SVN_CL__ACCEPT_WORKING },
416 /* These options use the same code since they only occur in
417 * distinct conflict scenarios. */
418 { "u", svn_client_conflict_option_update_move_destination },
419 { "u", svn_client_conflict_option_update_any_moved_away_children },
421 /* Options for incoming add vs local add. */
422 { "i", svn_client_conflict_option_incoming_add_ignore },
424 /* Options for incoming file add vs local file add upon merge. */
425 { "m", svn_client_conflict_option_incoming_added_file_text_merge },
426 { "M", svn_client_conflict_option_incoming_added_file_replace_and_merge },
428 /* Options for incoming dir add vs local dir add upon merge. */
429 { "m", svn_client_conflict_option_incoming_added_dir_merge },
430 { "R", svn_client_conflict_option_incoming_added_dir_replace },
431 { "M", svn_client_conflict_option_incoming_added_dir_replace_and_merge },
433 /* Options for incoming delete vs any. */
434 { "i", svn_client_conflict_option_incoming_delete_ignore },
435 { "a", svn_client_conflict_option_incoming_delete_accept },
437 /* Options for incoming move vs local edit. */
438 { "m", svn_client_conflict_option_incoming_move_file_text_merge },
439 { "m", svn_client_conflict_option_incoming_move_dir_merge },
441 /* Options for local move vs incoming edit. */
442 { "m", svn_client_conflict_option_local_move_file_text_merge },
447 /* Extra resolver options offered by 'svn' for any conflict. */
448 static const client_option_t extra_resolver_options[] =
450 /* Translators: keep long_desc below 70 characters (wrap with a left
451 margin of 9 spaces if needed) */
452 { "q", N_("Quit resolution"), N_("postpone all remaining conflicts"),
453 svn_client_conflict_option_postpone },
458 /* Additional resolver options offered by 'svn' for a text conflict. */
459 static const client_option_t extra_resolver_options_text[] =
461 /* Translators: keep long_desc below 70 characters (wrap with a left
462 margin of 9 spaces if needed) */
463 { "e", N_("Edit file"), N_("change merged file in an editor"),
464 svn_client_conflict_option_undefined,
465 SVN_CL__ACCEPT_EDIT },
466 { "df", N_("Show diff"), N_("show all changes made to merged file"),
467 svn_client_conflict_option_undefined},
468 { "dc", N_("Display conflict"), N_("show all conflicts "
469 "(ignoring merged version)"),
470 svn_client_conflict_option_undefined },
471 { "m", N_("Merge"), N_("use merge tool to resolve conflict"),
472 svn_client_conflict_option_undefined },
473 { "l", N_("Launch tool"), N_("launch external merge tool to resolve "
475 svn_client_conflict_option_undefined,
476 SVN_CL__ACCEPT_LAUNCH },
477 { "i", N_("Internal merge tool"), N_("use built-in merge tool to "
479 svn_client_conflict_option_undefined },
480 { "s", N_("Show all options"), N_("show this list (also 'h', '?')"),
481 svn_client_conflict_option_undefined },
485 /* Additional resolver options offered by 'svn' for a property conflict. */
486 static const client_option_t extra_resolver_options_prop[] =
488 /* Translators: keep long_desc below 70 characters (wrap with a left
489 margin of 9 spaces if needed) */
490 { "dc", N_("Display conflict"), N_("show conflicts in this property"),
491 svn_client_conflict_option_undefined },
492 { "e", N_("Edit property"), N_("change merged property value in an "
494 svn_client_conflict_option_undefined,
495 SVN_CL__ACCEPT_EDIT },
496 { "h", N_("Help"), N_("show this help (also '?')"),
497 svn_client_conflict_option_undefined },
501 /* Additional resolver options offered by 'svn' for a tree conflict. */
502 static const client_option_t extra_resolver_options_tree[] =
504 /* Translators: keep long_desc below 70 characters (wrap with a left
505 margin of 9 spaces if needed) */
506 { "d", N_("Set repository move destination path"),
507 N_("pick repository move target from list of possible targets"),
508 svn_client_conflict_option_undefined },
510 { "w", N_("Set working copy move destination path"),
511 N_("pick working copy move target from list of possible targets"),
512 svn_client_conflict_option_undefined },
514 { "h", N_("Help"), N_("show this help (also '?')"),
515 svn_client_conflict_option_undefined },
521 /* Return a pointer to the option description in OPTIONS matching the
522 * one- or two-character OPTION_CODE. Return NULL if not found. */
523 static const client_option_t *
524 find_option(const apr_array_header_t *options,
525 const char *option_code)
529 for (i = 0; i < options->nelts; i++)
531 const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *);
533 /* Ignore code "" (blank lines) which is not a valid answer. */
534 if (opt->code[0] && strcmp(opt->code, option_code) == 0)
540 /* Find the first recommended option in OPTIONS. */
541 static const client_option_t *
542 find_recommended_option(const apr_array_header_t *options)
546 for (i = 0; i < options->nelts; i++)
548 const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *);
550 /* Ignore code "" (blank lines) which is not a valid answer. */
551 if (opt->code[0] && opt->is_recommended)
557 /* Return a pointer to the client_option_t in OPTIONS matching the ID of
558 * conflict option BUILTIN_OPTION. @a out will be set to NULL if the
559 * option was not found. */
561 find_option_by_builtin(client_option_t **out,
562 svn_client_conflict_t *conflict,
563 const resolver_option_t *options,
564 svn_client_conflict_option_t *builtin_option,
565 apr_pool_t *result_pool,
566 apr_pool_t *scratch_pool)
568 const resolver_option_t *opt;
569 svn_client_conflict_option_id_t id;
570 svn_client_conflict_option_id_t recommended_id;
572 id = svn_client_conflict_option_get_id(builtin_option);
573 recommended_id = svn_client_conflict_get_recommended_option_id(conflict);
575 for (opt = options; opt->code; opt++)
577 if (opt->choice == id)
579 client_option_t *client_opt;
581 client_opt = apr_pcalloc(result_pool, sizeof(*client_opt));
582 client_opt->choice = id;
583 client_opt->code = opt->code;
584 client_opt->label = svn_client_conflict_option_get_label(
587 client_opt->long_desc = svn_client_conflict_option_get_description(
590 client_opt->accept_arg = opt->accept_arg;
591 client_opt->is_recommended =
592 (recommended_id != svn_client_conflict_option_unspecified &&
593 id == recommended_id);
606 /* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
607 * non-null, select only the options whose codes are mentioned in it. */
609 prompt_string(const apr_array_header_t *options,
610 const char *const *option_codes,
613 const char *result = _("Select:");
614 int left_margin = svn_utf_cstring_utf8_width(result);
615 const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
616 int this_line_len = left_margin;
617 svn_boolean_t first = TRUE;
622 const client_option_t *opt;
630 opt = find_option(options, *option_codes++);
636 if (i >= options->nelts)
638 opt = APR_ARRAY_IDX(options, i, client_option_t *);
643 result = apr_pstrcat(pool, result, ",", SVN_VA_NULL);
644 s = apr_psprintf(pool, " (%s) %s", opt->code,
645 opt->label ? opt->label : opt->long_desc);
646 slen = svn_utf_cstring_utf8_width(s);
647 /* Break the line if adding the next option would make it too long */
648 if (this_line_len + slen > MAX_PROMPT_WIDTH)
650 result = apr_pstrcat(pool, result, line_sep, SVN_VA_NULL);
651 this_line_len = left_margin;
653 result = apr_pstrcat(pool, result, s, SVN_VA_NULL);
654 this_line_len += slen;
657 return apr_pstrcat(pool, result, ": ", SVN_VA_NULL);
660 /* Return a help string listing the OPTIONS. */
662 help_string(const char **result,
663 const apr_array_header_t *options,
666 apr_pool_t *iterpool;
670 iterpool = svn_pool_create(pool);
671 for (i = 0; i < options->nelts; i++)
673 const client_option_t *opt;
674 svn_pool_clear(iterpool);
676 opt = APR_ARRAY_IDX(options, i,
679 /* Append a line describing OPT, or a blank line if its code is "". */
682 const char *s = apr_psprintf(pool, " (%s)", opt->code);
685 *result = apr_psprintf(pool, "%s%-6s - %s [%s]\n",
686 *result, s, opt->long_desc,
689 *result = apr_psprintf(pool, "%s%-6s - %s\n", *result, s,
694 *result = apr_pstrcat(pool, *result, "\n", SVN_VA_NULL);
697 svn_pool_destroy(iterpool);
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 * CONFLICT_DESCRIPTION (if not NULL) and help (on stderr) and return with
715 prompt_user(const client_option_t **opt,
716 const apr_array_header_t *conflict_options,
717 const char *const *options_to_show,
718 const char *conflict_description,
720 apr_pool_t *scratch_pool)
723 = prompt_string(conflict_options, options_to_show, scratch_pool);
726 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
727 if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
731 if (conflict_description)
732 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
733 conflict_description));
734 SVN_ERR(help_string(&helpstr, conflict_options, scratch_pool));
735 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", helpstr));
740 *opt = find_option(conflict_options, answer);
743 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
744 _("Unrecognized option.\n\n")));
750 /* Set *OPTIONS to an array of resolution options for CONFLICT. */
752 build_text_conflict_options(apr_array_header_t **options,
753 svn_client_conflict_t *conflict,
754 svn_client_ctx_t *ctx,
755 svn_boolean_t is_binary,
756 apr_pool_t *result_pool,
757 apr_pool_t *scratch_pool)
759 const client_option_t *o;
760 apr_array_header_t *builtin_options;
763 apr_pool_t *iterpool;
765 SVN_ERR(svn_client_conflict_text_get_resolution_options(&builtin_options,
769 nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options);
771 nopt += ARRAY_LEN(extra_resolver_options_text);
772 *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
774 iterpool = svn_pool_create(scratch_pool);
775 for (i = 0; i < builtin_options->nelts; i++)
777 client_option_t *opt;
778 svn_client_conflict_option_t *builtin_option;
780 svn_pool_clear(iterpool);
781 builtin_option = APR_ARRAY_IDX(builtin_options, i,
782 svn_client_conflict_option_t *);
783 SVN_ERR(find_option_by_builtin(&opt, conflict,
784 builtin_resolver_options,
789 continue; /* ### unknown option -- assign a code dynamically? */
791 APR_ARRAY_PUSH(*options, client_option_t *) = opt;
794 for (o = extra_resolver_options; o->code; o++)
795 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
798 for (o = extra_resolver_options_text; o->code; o++)
799 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
802 svn_pool_destroy(iterpool);
807 /* Mark CONFLICT as resolved to resolution option with ID OPTION_ID.
808 * If TEXT_CONFLICTED is true, resolve text conflicts described by CONFLICT.
809 * IF PROPNAME is not NULL, mark the conflict in the specified property as
810 * resolved. If PROPNAME is "", mark all property conflicts described by
811 * CONFLICT as resolved.
812 * If TREE_CONFLICTED is true, resolve tree conflicts described by CONFLICT.
813 * Adjust CONFLICT_STATS as necessary (PATH_PREFIX is needed for this step). */
815 mark_conflict_resolved(svn_client_conflict_t *conflict,
816 svn_client_conflict_option_id_t option_id,
817 svn_boolean_t text_conflicted,
818 const char *propname,
819 svn_boolean_t tree_conflicted,
820 const char *path_prefix,
821 svn_cl__conflict_stats_t *conflict_stats,
822 svn_client_ctx_t *ctx,
823 apr_pool_t *scratch_pool)
825 const char *local_relpath
826 = svn_cl__local_style_skip_ancestor(
827 path_prefix, svn_client_conflict_get_local_abspath(conflict),
832 SVN_ERR(svn_client_conflict_text_resolve_by_id(conflict, option_id,
834 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
835 svn_wc_conflict_kind_text);
840 SVN_ERR(svn_client_conflict_prop_resolve_by_id(conflict, propname,
843 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
844 svn_wc_conflict_kind_property);
849 SVN_ERR(svn_client_conflict_tree_resolve_by_id(conflict, option_id,
851 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
852 svn_wc_conflict_kind_tree);
858 /* Ask the user what to do about the text conflict described by CONFLICT
859 * and either resolve the conflict accordingly or postpone resolution.
860 * SCRATCH_POOL is used for temporary allocations. */
862 handle_text_conflict(svn_boolean_t *resolved,
863 svn_boolean_t *postponed,
865 svn_boolean_t *printed_description,
866 svn_client_conflict_t *conflict,
867 const char *path_prefix,
868 svn_cmdline_prompt_baton_t *pb,
869 const char *editor_cmd,
871 svn_cl__conflict_stats_t *conflict_stats,
872 svn_client_ctx_t *ctx,
873 apr_pool_t *scratch_pool)
875 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
876 svn_boolean_t diff_allowed = FALSE;
877 /* Have they done something that might have affected the merged file? */
878 svn_boolean_t performed_edit = FALSE;
879 /* Have they done *something* (edit, look at diff, etc) to
880 give them a rational basis for choosing (r)esolved? */
881 svn_boolean_t knows_something = FALSE;
882 const char *local_relpath;
883 const char *local_abspath = svn_client_conflict_get_local_abspath(conflict);
884 const char *mime_type = svn_client_conflict_text_get_mime_type(conflict);
885 svn_boolean_t is_binary = mime_type ? svn_mime_type_is_binary(mime_type)
887 const char *base_abspath;
888 const char *my_abspath;
889 const char *their_abspath;
890 const char *merged_abspath = svn_client_conflict_get_local_abspath(conflict);
891 apr_array_header_t *text_conflict_options;
892 svn_client_conflict_option_id_t option_id;
894 option_id = svn_client_conflict_option_unspecified;
896 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
897 &base_abspath, &their_abspath,
898 conflict, scratch_pool,
901 local_relpath = svn_cl__local_style_skip_ancestor(path_prefix,
905 if (!*printed_description)
908 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
909 _("Merge conflict discovered in binary "
913 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
914 _("Merge conflict discovered in file '%s'.\n"),
916 *printed_description = TRUE;
919 /* ### TODO This whole feature availability check is grossly outdated.
920 DIFF_ALLOWED needs either to be redefined or to go away.
923 /* Diffing can happen between base and merged, to show conflict
924 markers to the user (this is the typical 3-way merge
925 scenario), or if no base is available, we can show a diff
926 between mine and theirs. */
928 ((merged_abspath && base_abspath)
929 || (!base_abspath && my_abspath && their_abspath)))
932 SVN_ERR(build_text_conflict_options(&text_conflict_options, conflict, ctx,
933 is_binary, scratch_pool, scratch_pool));
936 const char *suggested_options[9]; /* filled statically below */
937 const char **next_option = suggested_options;
938 const client_option_t *opt;
940 svn_pool_clear(iterpool);
942 *next_option++ = "p";
945 /* We need one more path for this feature. */
947 *next_option++ = "df";
949 *next_option++ = "e";
951 /* We need one more path for this feature. */
953 *next_option++ = "m";
956 *next_option++ = "r";
960 if (knows_something || is_binary)
961 *next_option++ = "r";
963 /* The 'mine-full' option selects the ".mine" file for texts or
964 * the current working directory file for binary files. */
965 if (my_abspath || is_binary)
966 *next_option++ = "mf";
968 *next_option++ = "tf";
970 *next_option++ = "s";
971 *next_option++ = NULL;
973 SVN_ERR(prompt_user(&opt, text_conflict_options, suggested_options,
974 NULL, pb, iterpool));
978 if (strcmp(opt->code, "q") == 0)
980 option_id = opt->choice;
984 else if (strcmp(opt->code, "s") == 0)
988 SVN_ERR(help_string(&helpstr, text_conflict_options, iterpool));
989 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
992 else if (strcmp(opt->code, "dc") == 0)
996 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
997 _("Invalid option; cannot "
998 "display conflicts for a "
999 "binary file.\n\n")));
1002 else if (! (my_abspath && base_abspath && their_abspath))
1004 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1005 _("Invalid option; original "
1006 "files not available.\n\n")));
1009 SVN_ERR(show_conflicts(conflict,
1013 knows_something = TRUE;
1015 else if (strcmp(opt->code, "df") == 0)
1017 /* Re-check preconditions. */
1018 if (! diff_allowed || ! my_abspath)
1020 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1021 _("Invalid option; there's no "
1022 "merged version to diff.\n\n")));
1026 SVN_ERR(show_diff(conflict, merged_abspath, path_prefix,
1027 pb->cancel_func, pb->cancel_baton,
1029 knows_something = TRUE;
1031 else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
1033 SVN_ERR(open_editor(&performed_edit, merged_abspath, editor_cmd,
1036 knows_something = TRUE;
1038 else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
1039 strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
1043 /* Re-check preconditions. */
1046 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1047 _("Invalid option; there's no "
1048 "base path to merge.\n\n")));
1052 err = svn_cl__merge_file_externally(base_abspath,
1056 local_abspath, config,
1060 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
1062 svn_boolean_t remains_in_conflict = TRUE;
1064 /* Try the internal merge tool. */
1065 svn_error_clear(err);
1066 SVN_ERR(svn_cl__merge_file(&remains_in_conflict,
1078 knows_something = !remains_in_conflict;
1080 else if (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
1083 const char *message;
1085 message = svn_err_best_message(err, buf, sizeof(buf));
1086 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1088 svn_error_clear(err);
1092 return svn_error_trace(err);
1096 /* The external merge tool's exit code was either 0 or 1.
1097 * The tool may leave the file conflicted by exiting with
1098 * exit code 1, and we allow the user to mark the conflict
1099 * resolved in this case. */
1100 performed_edit = TRUE;
1101 knows_something = TRUE;
1104 else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
1106 /* ### This check should be earlier as it's nasty to offer an option
1107 * and then when the user chooses it say 'Invalid option'. */
1108 /* ### 'merged_abspath' shouldn't be necessary *before* we launch the
1109 * resolver: it should be the *result* of doing so. */
1110 if (base_abspath && their_abspath && my_abspath && merged_abspath)
1114 const char *message;
1116 err = svn_cl__merge_file_externally(base_abspath,
1121 config, NULL, iterpool);
1122 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL ||
1123 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
1125 message = svn_err_best_message(err, buf, sizeof(buf));
1126 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n",
1128 svn_error_clear(err);
1131 return svn_error_trace(err);
1133 performed_edit = TRUE;
1136 knows_something = TRUE;
1139 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1140 _("Invalid option.\n\n")));
1142 else if (strcmp(opt->code, "i") == 0)
1144 svn_boolean_t remains_in_conflict = TRUE;
1146 SVN_ERR(svn_cl__merge_file(&remains_in_conflict,
1159 if (!remains_in_conflict)
1160 knows_something = TRUE;
1162 else if (opt->choice != svn_client_conflict_option_undefined)
1164 if ((opt->choice == svn_client_conflict_option_working_text_where_conflicted
1165 || opt->choice == svn_client_conflict_option_incoming_text_where_conflicted)
1168 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1169 _("Invalid option; cannot choose "
1170 "based on conflicts in a "
1171 "binary file.\n\n")));
1175 /* We only allow the user accept the merged version of
1176 the file if they've edited it, or at least looked at
1178 if (opt->choice == svn_client_conflict_option_merged_text
1179 && ! knows_something && diff_allowed)
1181 SVN_ERR(svn_cmdline_fprintf(
1183 _("Invalid option; use diff/edit/merge/launch "
1184 "before choosing 'mark resolved'.\n\n")));
1188 option_id = opt->choice;
1192 svn_pool_destroy(iterpool);
1194 if (option_id != svn_client_conflict_option_unspecified &&
1195 option_id != svn_client_conflict_option_postpone)
1197 SVN_ERR(mark_conflict_resolved(conflict, option_id,
1199 path_prefix, conflict_stats,
1200 ctx, scratch_pool));
1206 *postponed = (option_id == svn_client_conflict_option_postpone);
1209 return SVN_NO_ERROR;
1212 /* Set *OPTIONS to an array of resolution options for CONFLICT. */
1213 static svn_error_t *
1214 build_prop_conflict_options(apr_array_header_t **options,
1215 svn_client_conflict_t *conflict,
1216 svn_client_ctx_t *ctx,
1217 apr_pool_t *result_pool,
1218 apr_pool_t *scratch_pool)
1220 const client_option_t *o;
1221 apr_array_header_t *builtin_options;
1224 apr_pool_t *iterpool;
1226 SVN_ERR(svn_client_conflict_prop_get_resolution_options(&builtin_options,
1230 nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options) +
1231 ARRAY_LEN(extra_resolver_options_prop);
1232 *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
1234 iterpool = svn_pool_create(scratch_pool);
1235 for (i = 0; i < builtin_options->nelts; i++)
1237 client_option_t *opt;
1238 svn_client_conflict_option_t *builtin_option;
1240 svn_pool_clear(iterpool);
1241 builtin_option = APR_ARRAY_IDX(builtin_options, i,
1242 svn_client_conflict_option_t *);
1243 SVN_ERR(find_option_by_builtin(&opt, conflict,
1244 builtin_resolver_options,
1249 continue; /* ### unknown option -- assign a code dynamically? */
1251 APR_ARRAY_PUSH(*options, client_option_t *) = opt;
1254 svn_pool_destroy(iterpool);
1256 for (o = extra_resolver_options; o->code; o++)
1257 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1258 for (o = extra_resolver_options_prop; o->code; o++)
1259 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1261 return SVN_NO_ERROR;
1264 /* Ask the user what to do about the conflicted property PROPNAME described
1265 * by CONFLICT and return the corresponding resolution option in *OPTION.
1266 * SCRATCH_POOL is used for temporary allocations. */
1267 static svn_error_t *
1268 handle_one_prop_conflict(svn_client_conflict_option_t **option,
1269 svn_boolean_t *quit,
1270 const char *path_prefix,
1271 svn_cmdline_prompt_baton_t *pb,
1272 const char *editor_cmd,
1274 svn_client_conflict_t *conflict,
1275 const char *propname,
1276 svn_client_ctx_t *ctx,
1277 apr_pool_t *result_pool,
1278 apr_pool_t *scratch_pool)
1280 apr_pool_t *iterpool;
1281 const char *description;
1282 const svn_string_t *merged_propval = NULL;
1283 svn_boolean_t resolved_allowed = FALSE;
1284 const svn_string_t *base_propval;
1285 const svn_string_t *my_propval;
1286 const svn_string_t *their_propval;
1287 apr_array_header_t *resolution_options;
1288 apr_array_header_t *prop_conflict_options;
1290 SVN_ERR(svn_client_conflict_prop_get_propvals(NULL, &my_propval,
1291 &base_propval, &their_propval,
1295 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1296 _("Conflict for property '%s' discovered"
1299 svn_cl__local_style_skip_ancestor(
1301 svn_client_conflict_get_local_abspath(conflict),
1303 SVN_ERR(svn_client_conflict_prop_get_description(&description, conflict,
1304 scratch_pool, scratch_pool));
1305 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", description));
1307 SVN_ERR(svn_client_conflict_prop_get_resolution_options(&resolution_options,
1311 SVN_ERR(build_prop_conflict_options(&prop_conflict_options, conflict, ctx,
1312 scratch_pool, scratch_pool));
1313 iterpool = svn_pool_create(scratch_pool);
1316 const client_option_t *opt;
1317 const char *suggested_options[9]; /* filled statically below */
1318 const char **next_option = suggested_options;
1320 *next_option++ = "p";
1321 *next_option++ = "mf";
1322 *next_option++ = "tf";
1323 *next_option++ = "dc";
1324 *next_option++ = "e";
1325 if (resolved_allowed)
1326 *next_option++ = "r";
1327 *next_option++ = "q";
1328 *next_option++ = "h";
1329 *next_option++ = NULL;
1331 svn_pool_clear(iterpool);
1333 SVN_ERR(prompt_user(&opt, prop_conflict_options, suggested_options,
1334 NULL, pb, iterpool));
1338 if (strcmp(opt->code, "q") == 0)
1340 *option = svn_client_conflict_option_find_by_id(resolution_options,
1345 else if (strcmp(opt->code, "dc") == 0)
1347 SVN_ERR(show_prop_conflict(base_propval, my_propval, their_propval,
1349 pb->cancel_func, pb->cancel_baton,
1352 else if (strcmp(opt->code, "e") == 0)
1354 SVN_ERR(edit_prop_conflict(&merged_propval,
1355 base_propval, my_propval, their_propval,
1356 editor_cmd, config, pb,
1357 result_pool, scratch_pool));
1358 resolved_allowed = (merged_propval != NULL);
1360 else if (strcmp(opt->code, "r") == 0)
1362 if (! resolved_allowed)
1364 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1365 _("Invalid option; please edit the property "
1370 *option = svn_client_conflict_option_find_by_id(
1372 svn_client_conflict_option_merged_text);
1373 svn_client_conflict_option_set_merged_propval(*option,
1377 else if (opt->choice != svn_client_conflict_option_undefined)
1379 *option = svn_client_conflict_option_find_by_id(resolution_options,
1384 svn_pool_destroy(iterpool);
1386 return SVN_NO_ERROR;
1389 /* Ask the user what to do about the property conflicts described by CONFLICT
1390 * and either resolve them accordingly or postpone resolution.
1391 * SCRATCH_POOL is used for temporary allocations. */
1392 static svn_error_t *
1393 handle_prop_conflicts(svn_boolean_t *resolved,
1394 svn_boolean_t *postponed,
1395 svn_boolean_t *quit,
1396 const svn_string_t **merged_value,
1397 const char *path_prefix,
1398 svn_cmdline_prompt_baton_t *pb,
1399 const char *editor_cmd,
1401 svn_client_conflict_t *conflict,
1402 svn_cl__conflict_stats_t *conflict_stats,
1403 svn_client_ctx_t *ctx,
1404 apr_pool_t *result_pool,
1405 apr_pool_t *scratch_pool)
1407 apr_array_header_t *props_conflicted;
1408 apr_pool_t *iterpool;
1412 SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
1413 conflict, scratch_pool,
1416 iterpool = svn_pool_create(scratch_pool);
1417 for (i = 0; i < props_conflicted->nelts; i++)
1419 const char *propname = APR_ARRAY_IDX(props_conflicted, i, const char *);
1420 svn_client_conflict_option_t *option;
1421 svn_client_conflict_option_id_t option_id;
1423 svn_pool_clear(iterpool);
1425 SVN_ERR(handle_one_prop_conflict(&option, quit, path_prefix, pb,
1426 editor_cmd, config, conflict, propname,
1428 iterpool, iterpool));
1429 option_id = svn_client_conflict_option_get_id(option);
1431 if (option_id != svn_client_conflict_option_unspecified &&
1432 option_id != svn_client_conflict_option_postpone)
1434 const char *local_relpath =
1435 svn_cl__local_style_skip_ancestor(
1436 path_prefix, svn_client_conflict_get_local_abspath(conflict),
1439 SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option,
1441 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
1442 svn_wc_conflict_kind_property);
1447 *postponed = (option_id == svn_client_conflict_option_postpone);
1452 svn_pool_destroy(iterpool);
1454 /* Indicate success if no property conflicts remain. */
1455 *resolved = (nresolved == props_conflicted->nelts);
1457 return SVN_NO_ERROR;
1460 /* Set *OPTIONS to an array of resolution options for CONFLICT. */
1461 static svn_error_t *
1462 build_tree_conflict_options(
1463 apr_array_header_t **options,
1464 apr_array_header_t **possible_moved_to_repos_relpaths,
1465 apr_array_header_t **possible_moved_to_abspaths,
1466 svn_boolean_t *all_options_are_dumb,
1467 svn_client_conflict_t *conflict,
1468 svn_client_ctx_t *ctx,
1469 apr_pool_t *result_pool,
1470 apr_pool_t *scratch_pool)
1472 const client_option_t *o;
1473 apr_array_header_t *builtin_options;
1476 int next_unknown_option_code = 1;
1477 apr_pool_t *iterpool;
1479 if (all_options_are_dumb != NULL)
1480 *all_options_are_dumb = TRUE;
1482 SVN_ERR(svn_client_conflict_tree_get_resolution_options(&builtin_options,
1486 nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options_tree) +
1487 ARRAY_LEN(extra_resolver_options);
1488 *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
1489 *possible_moved_to_abspaths = NULL;
1490 *possible_moved_to_repos_relpaths = NULL;
1492 iterpool = svn_pool_create(scratch_pool);
1493 for (i = 0; i < builtin_options->nelts; i++)
1495 client_option_t *opt;
1496 svn_client_conflict_option_t *builtin_option;
1497 svn_client_conflict_option_id_t id;
1499 svn_pool_clear(iterpool);
1500 builtin_option = APR_ARRAY_IDX(builtin_options, i,
1501 svn_client_conflict_option_t *);
1502 SVN_ERR(find_option_by_builtin(&opt, conflict,
1503 builtin_resolver_options,
1509 /* Unkown option. Assign a dynamic option code. */
1510 opt = apr_pcalloc(result_pool, sizeof(*opt));
1511 opt->code = apr_psprintf(result_pool, "%d", next_unknown_option_code);
1512 next_unknown_option_code++;
1513 opt->label = svn_client_conflict_option_get_label(builtin_option,
1515 opt->long_desc = svn_client_conflict_option_get_description(
1516 builtin_option, result_pool);
1517 opt->choice = svn_client_conflict_option_get_id(builtin_option);
1518 opt->accept_arg = NULL;
1521 APR_ARRAY_PUSH(*options, client_option_t *) = opt;
1523 id = svn_client_conflict_option_get_id(builtin_option);
1525 /* Check if we got a "smart" tree conflict option. */
1526 if (all_options_are_dumb != NULL &&
1527 *all_options_are_dumb &&
1528 id != svn_client_conflict_option_postpone &&
1529 id != svn_client_conflict_option_accept_current_wc_state)
1530 *all_options_are_dumb = FALSE;
1532 if (id == svn_client_conflict_option_incoming_move_file_text_merge ||
1533 id == svn_client_conflict_option_incoming_move_dir_merge)
1536 svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
1537 possible_moved_to_repos_relpaths, builtin_option,
1538 result_pool, iterpool));
1539 SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates(
1540 possible_moved_to_abspaths, builtin_option,
1541 result_pool, iterpool));
1545 svn_pool_destroy(iterpool);
1547 for (o = extra_resolver_options_tree; o->code; o++)
1549 /* Add move target choice options only if there are multiple
1550 * move targets to choose from. */
1551 if (strcmp(o->code, "d") == 0 &&
1552 (*possible_moved_to_repos_relpaths == NULL ||
1553 (*possible_moved_to_repos_relpaths)->nelts <= 1))
1555 if (strcmp(o->code, "w") == 0 &&
1556 (*possible_moved_to_abspaths == NULL ||
1557 (*possible_moved_to_abspaths)->nelts <= 1))
1560 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1562 for (o = extra_resolver_options; o->code; o++)
1563 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1565 return SVN_NO_ERROR;
1568 /* Make the user select a move target path for the moved-away VICTIM_ABSPATH. */
1569 static svn_error_t *
1570 prompt_move_target_path(int *preferred_move_target_idx,
1571 apr_array_header_t *possible_moved_to_paths,
1572 svn_boolean_t paths_are_local,
1573 svn_cmdline_prompt_baton_t *pb,
1574 const char *victim_abspath,
1575 svn_client_ctx_t *ctx,
1576 apr_pool_t *scratch_pool)
1578 const char *move_targets_prompt = "";
1579 const char *move_targets_list = "";
1580 const char *wcroot_abspath;
1581 const char *victim_relpath;
1584 apr_pool_t *iterpool;
1586 SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, victim_abspath,
1587 ctx, scratch_pool, scratch_pool));
1588 victim_relpath = svn_cl__local_style_skip_ancestor(wcroot_abspath,
1591 iterpool = svn_pool_create(scratch_pool);
1593 /* Build the prompt. */
1594 for (i = 0; i < possible_moved_to_paths->nelts; i++)
1596 svn_pool_clear(iterpool);
1598 if (paths_are_local)
1600 const char *moved_to_abspath;
1601 const char *moved_to_relpath;
1603 moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_paths, i,
1605 moved_to_relpath = svn_cl__local_style_skip_ancestor(
1606 wcroot_abspath, moved_to_abspath, iterpool),
1607 move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '%s'\n",
1608 move_targets_list, i + 1,
1613 const char *moved_to_repos_relpath;
1615 moved_to_repos_relpath = APR_ARRAY_IDX(possible_moved_to_paths, i,
1617 move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '^/%s'\n",
1618 move_targets_list, i + 1,
1619 moved_to_repos_relpath);
1622 if (paths_are_local)
1623 move_targets_prompt =
1624 apr_psprintf(scratch_pool,
1625 _("Possible working copy destinations for moved-away '%s' "
1627 "Only one destination can be a move; the others are "
1629 "Specify the correct move target path by number: "),
1630 victim_relpath, move_targets_list);
1632 move_targets_prompt =
1633 apr_psprintf(scratch_pool,
1634 _("Possible repository destinations for moved-away '%s' "
1636 "Only one destination can be a move; the others are "
1638 "Specify the correct move target path by number: "),
1639 victim_relpath, move_targets_list);
1641 /* Keep asking the user until we got a valid choice. */
1647 svn_pool_clear(iterpool);
1649 SVN_ERR(svn_cmdline_prompt_user2(&answer, move_targets_prompt,
1651 err = svn_cstring_strtoi64(&idx, answer, 1,
1652 possible_moved_to_paths->nelts, 10);
1657 svn_cmdline_fprintf(stderr, iterpool, "%s\n",
1658 svn_err_best_message(err, buf, sizeof(buf)));
1659 svn_error_clear(err);
1666 svn_pool_destroy(iterpool);
1668 SVN_ERR_ASSERT((idx - 1) == (int)(idx - 1));
1669 *preferred_move_target_idx = (int)(idx - 1);
1670 return SVN_NO_ERROR;
1673 /* Ask the user what to do about the tree conflict described by CONFLICT
1674 * and either resolve the conflict accordingly or postpone resolution.
1675 * SCRATCH_POOL is used for temporary allocations. */
1676 static svn_error_t *
1677 handle_tree_conflict(svn_boolean_t *resolved,
1678 svn_boolean_t *postponed,
1679 svn_boolean_t *quit,
1680 svn_boolean_t *printed_description,
1681 svn_client_conflict_t *conflict,
1682 const char *path_prefix,
1683 svn_cmdline_prompt_baton_t *pb,
1684 svn_cl__conflict_stats_t *conflict_stats,
1685 svn_client_ctx_t *ctx,
1686 apr_pool_t *scratch_pool)
1688 apr_pool_t *iterpool;
1689 apr_array_header_t *tree_conflict_options;
1690 svn_client_conflict_option_id_t option_id;
1691 const char *local_abspath;
1692 const char *conflict_description;
1693 const char *local_change_description;
1694 const char *incoming_change_description;
1695 apr_array_header_t *possible_moved_to_repos_relpaths;
1696 apr_array_header_t *possible_moved_to_abspaths;
1697 svn_boolean_t all_options_are_dumb;
1698 const struct client_option_t *recommended_option;
1699 svn_boolean_t repos_move_target_chosen = FALSE;
1700 svn_boolean_t wc_move_target_chosen = FALSE;
1702 option_id = svn_client_conflict_option_unspecified;
1703 local_abspath = svn_client_conflict_get_local_abspath(conflict);
1705 /* Always show the best possible conflict description and options. */
1706 SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, scratch_pool));
1708 SVN_ERR(svn_client_conflict_tree_get_description(
1709 &incoming_change_description, &local_change_description,
1710 conflict, ctx, scratch_pool, scratch_pool));
1711 conflict_description = apr_psprintf(scratch_pool, "%s\n%s",
1712 incoming_change_description,
1713 local_change_description);
1714 if (!*printed_description)
1715 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1716 _("Tree conflict on '%s':\n%s\n"),
1717 svn_cl__local_style_skip_ancestor(
1718 path_prefix, local_abspath, scratch_pool),
1719 conflict_description));
1721 SVN_ERR(build_tree_conflict_options(&tree_conflict_options,
1722 &possible_moved_to_repos_relpaths,
1723 &possible_moved_to_abspaths,
1724 &all_options_are_dumb,
1726 scratch_pool, scratch_pool));
1728 /* Try a recommended resolution option before prompting. */
1729 recommended_option = find_recommended_option(tree_conflict_options);
1730 if (recommended_option)
1733 apr_status_t root_cause;
1735 SVN_ERR(svn_cmdline_printf(scratch_pool,
1736 _("Applying recommended resolution '%s':\n"),
1737 recommended_option->label));
1739 err = mark_conflict_resolved(conflict, recommended_option->choice,
1741 path_prefix, conflict_stats,
1746 return SVN_NO_ERROR;
1749 root_cause = svn_error_root_cause(err)->apr_err;
1750 if (root_cause != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE &&
1751 root_cause != SVN_ERR_WC_OBSTRUCTED_UPDATE &&
1752 root_cause != SVN_ERR_WC_FOUND_CONFLICT)
1753 return svn_error_trace(err);
1755 /* Fall back to interactive prompting. */
1756 svn_error_clear(err);
1759 if (all_options_are_dumb)
1760 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1761 _("\nSubversion is not smart enough to resolve "
1762 "this tree conflict automatically!\nSee 'svn "
1763 "help resolve' for more information.\n\n")));
1765 iterpool = svn_pool_create(scratch_pool);
1768 const client_option_t *opt;
1770 svn_pool_clear(iterpool);
1772 if (!repos_move_target_chosen &&
1773 possible_moved_to_repos_relpaths &&
1774 possible_moved_to_repos_relpaths->nelts > 1)
1775 SVN_ERR(svn_cmdline_printf(scratch_pool,
1776 _("Ambiguous move destinations exist in the repository; "
1777 "try the 'd' option\n")));
1778 if (!wc_move_target_chosen && possible_moved_to_abspaths &&
1779 possible_moved_to_abspaths->nelts > 1)
1780 SVN_ERR(svn_cmdline_printf(scratch_pool,
1781 _("Ambiguous move destinations exist in the working copy; "
1782 "try the 'w' option\n")));
1784 SVN_ERR(prompt_user(&opt, tree_conflict_options, NULL,
1785 conflict_description, pb, iterpool));
1786 *printed_description = TRUE;
1790 if (strcmp(opt->code, "q") == 0)
1792 option_id = opt->choice;
1796 else if (strcmp(opt->code, "d") == 0)
1798 int preferred_move_target_idx;
1799 apr_array_header_t *options;
1800 svn_client_conflict_option_t *conflict_option;
1802 SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,
1803 possible_moved_to_repos_relpaths,
1805 pb, local_abspath, ctx, iterpool));
1807 /* Update preferred move target path. */
1808 SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options,
1814 svn_client_conflict_option_find_by_id(
1816 svn_client_conflict_option_incoming_move_file_text_merge);
1817 if (conflict_option == NULL)
1820 svn_client_conflict_option_find_by_id(
1821 options, svn_client_conflict_option_incoming_move_dir_merge);
1824 if (conflict_option)
1826 SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(
1827 conflict_option, preferred_move_target_idx,
1829 repos_move_target_chosen = TRUE;
1830 wc_move_target_chosen = FALSE;
1832 /* Update option description. */
1833 SVN_ERR(build_tree_conflict_options(
1834 &tree_conflict_options,
1835 &possible_moved_to_repos_relpaths,
1836 &possible_moved_to_abspaths,
1837 NULL, conflict, ctx,
1838 scratch_pool, scratch_pool));
1840 /* Update conflict description. */
1841 SVN_ERR(svn_client_conflict_tree_get_description(
1842 &incoming_change_description, &local_change_description,
1843 conflict, ctx, scratch_pool, scratch_pool));
1844 conflict_description = apr_psprintf(scratch_pool, "%s\n%s",
1845 incoming_change_description,
1846 local_change_description);
1850 else if (strcmp(opt->code, "w") == 0)
1852 int preferred_move_target_idx;
1853 apr_array_header_t *options;
1854 svn_client_conflict_option_t *conflict_option;
1856 SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,
1857 possible_moved_to_abspaths, TRUE,
1858 pb, local_abspath, ctx, iterpool));
1860 /* Update preferred move target path. */
1861 SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options,
1867 svn_client_conflict_option_find_by_id(
1869 svn_client_conflict_option_incoming_move_file_text_merge);
1870 if (conflict_option == NULL)
1873 svn_client_conflict_option_find_by_id(
1874 options, svn_client_conflict_option_incoming_move_dir_merge);
1877 if (conflict_option)
1879 SVN_ERR(svn_client_conflict_option_set_moved_to_abspath(
1880 conflict_option, preferred_move_target_idx, ctx,
1882 wc_move_target_chosen = TRUE;
1884 /* Update option description. */
1885 SVN_ERR(build_tree_conflict_options(
1886 &tree_conflict_options,
1887 &possible_moved_to_repos_relpaths,
1888 &possible_moved_to_abspaths,
1889 NULL, conflict, ctx,
1890 scratch_pool, scratch_pool));
1894 else if (opt->choice != svn_client_conflict_option_undefined)
1896 option_id = opt->choice;
1900 svn_pool_destroy(iterpool);
1901 if (option_id != svn_client_conflict_option_unspecified &&
1902 option_id != svn_client_conflict_option_postpone)
1904 SVN_ERR(mark_conflict_resolved(conflict, option_id,
1906 path_prefix, conflict_stats,
1907 ctx, scratch_pool));
1913 *postponed = (option_id == svn_client_conflict_option_postpone);
1916 return SVN_NO_ERROR;
1919 static svn_error_t *
1920 resolve_conflict_interactively(svn_boolean_t *resolved,
1921 svn_boolean_t *postponed,
1922 svn_boolean_t *quit,
1923 svn_boolean_t *external_failed,
1924 svn_boolean_t *printed_summary,
1925 svn_boolean_t *printed_description,
1926 svn_client_conflict_t *conflict,
1927 const char *editor_cmd,
1929 const char *path_prefix,
1930 svn_cmdline_prompt_baton_t *pb,
1931 svn_cl__conflict_stats_t *conflict_stats,
1932 svn_client_ctx_t *ctx,
1933 apr_pool_t *result_pool,
1934 apr_pool_t *scratch_pool)
1936 svn_boolean_t text_conflicted;
1937 apr_array_header_t *props_conflicted;
1938 svn_boolean_t tree_conflicted;
1939 const svn_string_t *merged_propval;
1941 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
1948 /* Print a summary of conflicts before starting interactive resolution */
1949 if (! *printed_summary)
1951 SVN_ERR(svn_cl__print_conflict_stats(conflict_stats, scratch_pool));
1952 *printed_summary = TRUE;
1957 && (svn_client_conflict_get_incoming_change(conflict) ==
1958 svn_wc_conflict_action_edit)
1959 && (svn_client_conflict_get_local_change(conflict) ==
1960 svn_wc_conflict_reason_edited))
1961 SVN_ERR(handle_text_conflict(resolved, postponed, quit, printed_description,
1962 conflict, path_prefix, pb, editor_cmd, config,
1963 conflict_stats, ctx, scratch_pool));
1964 if (props_conflicted->nelts > 0)
1965 SVN_ERR(handle_prop_conflicts(resolved, postponed, quit, &merged_propval,
1966 path_prefix, pb, editor_cmd, config, conflict,
1967 conflict_stats, ctx, result_pool, scratch_pool));
1968 if (tree_conflicted)
1969 SVN_ERR(handle_tree_conflict(resolved, postponed, quit, printed_description,
1970 conflict, path_prefix, pb, conflict_stats, ctx,
1973 return SVN_NO_ERROR;
1977 svn_cl__resolve_conflict(svn_boolean_t *quit,
1978 svn_boolean_t *external_failed,
1979 svn_boolean_t *printed_summary,
1980 svn_client_conflict_t *conflict,
1981 svn_cl__accept_t accept_which,
1982 const char *editor_cmd,
1983 const char *path_prefix,
1984 svn_cmdline_prompt_baton_t *pb,
1985 svn_cl__conflict_stats_t *conflict_stats,
1986 svn_client_ctx_t *ctx,
1987 apr_pool_t *scratch_pool)
1989 svn_boolean_t text_conflicted;
1990 apr_array_header_t *props_conflicted;
1991 svn_boolean_t tree_conflicted;
1992 const char *local_abspath;
1993 svn_client_conflict_option_id_t option_id;
1995 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
2001 local_abspath = svn_client_conflict_get_local_abspath(conflict);
2003 if (accept_which == svn_cl__accept_unspecified)
2005 option_id = svn_client_conflict_option_unspecified;
2007 else if (accept_which == svn_cl__accept_postpone)
2009 option_id = svn_client_conflict_option_postpone;
2011 else if (accept_which == svn_cl__accept_base)
2013 option_id = svn_client_conflict_option_base_text;
2015 else if (accept_which == svn_cl__accept_working)
2017 option_id = svn_client_conflict_option_merged_text;
2019 if (text_conflicted)
2021 const char *mime_type =
2022 svn_client_conflict_text_get_mime_type(conflict);
2024 /* There is no merged text for binary conflicts, behave as
2025 * if 'mine-full' was chosen. */
2026 if (mime_type && svn_mime_type_is_binary(mime_type))
2027 option_id = svn_client_conflict_option_working_text;
2029 else if (tree_conflicted)
2031 /* For tree conflicts, map 'working' to 'accept current working
2033 option_id = svn_client_conflict_option_accept_current_wc_state;
2036 else if (accept_which == svn_cl__accept_theirs_conflict)
2038 option_id = svn_client_conflict_option_incoming_text_where_conflicted;
2040 else if (accept_which == svn_cl__accept_mine_conflict)
2042 option_id = svn_client_conflict_option_working_text_where_conflicted;
2044 if (tree_conflicted)
2046 svn_wc_operation_t operation;
2048 operation = svn_client_conflict_get_operation(conflict);
2049 if (operation == svn_wc_operation_update ||
2050 operation == svn_wc_operation_switch)
2052 svn_wc_conflict_reason_t reason;
2054 reason = svn_client_conflict_get_local_change(conflict);
2055 if (reason == svn_wc_conflict_reason_moved_away)
2057 /* Map 'mine-conflict' to 'update move destination'. */
2059 svn_client_conflict_option_update_move_destination;
2061 else if (reason == svn_wc_conflict_reason_deleted ||
2062 reason == svn_wc_conflict_reason_replaced)
2064 svn_wc_conflict_action_t action;
2065 svn_node_kind_t node_kind;
2067 action = svn_client_conflict_get_incoming_change(conflict);
2069 svn_client_conflict_tree_get_victim_node_kind(conflict);
2071 if (action == svn_wc_conflict_action_edit &&
2072 node_kind == svn_node_dir)
2074 /* Map 'mine-conflict' to 'update any moved away
2077 svn_client_conflict_option_update_any_moved_away_children;
2083 else if (accept_which == svn_cl__accept_theirs_full)
2085 option_id = svn_client_conflict_option_incoming_text;
2087 else if (accept_which == svn_cl__accept_mine_full)
2089 option_id = svn_client_conflict_option_working_text;
2091 else if (accept_which == svn_cl__accept_edit)
2093 option_id = svn_client_conflict_option_unspecified;
2097 if (*external_failed)
2099 option_id = svn_client_conflict_option_postpone;
2105 err = svn_cmdline__edit_file_externally(local_abspath,
2109 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR ||
2110 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
2113 const char *message;
2115 message = svn_err_best_message(err, buf, sizeof(buf));
2116 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
2118 svn_error_clear(err);
2119 *external_failed = TRUE;
2122 return svn_error_trace(err);
2123 option_id = svn_client_conflict_option_merged_text;
2127 else if (accept_which == svn_cl__accept_launch)
2129 const char *base_abspath = NULL;
2130 const char *my_abspath = NULL;
2131 const char *their_abspath = NULL;
2133 option_id = svn_client_conflict_option_unspecified;
2135 if (text_conflicted)
2136 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
2139 conflict, scratch_pool,
2142 if (base_abspath && their_abspath && my_abspath && local_abspath)
2144 if (*external_failed)
2146 option_id = svn_client_conflict_option_postpone;
2150 svn_boolean_t remains_in_conflict;
2153 err = svn_cl__merge_file_externally(base_abspath, their_abspath,
2154 my_abspath, local_abspath,
2155 local_abspath, ctx->config,
2156 &remains_in_conflict,
2158 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL ||
2159 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
2162 const char *message;
2164 message = svn_err_best_message(err, buf, sizeof(buf));
2165 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
2167 *external_failed = TRUE;
2168 return svn_error_trace(err);
2171 return svn_error_trace(err);
2173 if (remains_in_conflict)
2174 option_id = svn_client_conflict_option_postpone;
2176 option_id = svn_client_conflict_option_merged_text;
2180 else if (accept_which == svn_cl__accept_recommended)
2182 svn_client_conflict_option_id_t recommended_id;
2184 if (tree_conflicted)
2185 SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx,
2187 recommended_id = svn_client_conflict_get_recommended_option_id(conflict);
2188 if (recommended_id != svn_client_conflict_option_unspecified)
2189 option_id = recommended_id;
2191 option_id = svn_client_conflict_option_postpone;
2194 SVN_ERR_MALFUNCTION();
2196 /* If we are in interactive mode and either the user gave no --accept
2197 * option or the option did not apply, then prompt. */
2198 if (option_id == svn_client_conflict_option_unspecified)
2200 svn_boolean_t resolved = FALSE;
2201 svn_boolean_t postponed = FALSE;
2202 svn_boolean_t printed_description = FALSE;
2207 while (!resolved && !postponed && !*quit)
2209 err = resolve_conflict_interactively(&resolved, &postponed, quit,
2212 &printed_description,
2214 editor_cmd, ctx->config,
2216 conflict_stats, ctx,
2217 scratch_pool, scratch_pool);
2218 if (err && err->apr_err == SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE)
2220 /* Conflict resolution has failed. Let the user try again.
2221 * It is always possible to break out of this loop with
2222 * the 'quit' or 'postpone' options. */
2223 svn_handle_warning2(stderr, err, "svn: ");
2224 svn_error_clear(err);
2230 else if (option_id != svn_client_conflict_option_postpone)
2231 SVN_ERR(mark_conflict_resolved(conflict, option_id,
2233 props_conflicted->nelts > 0 ? "" : NULL,
2235 path_prefix, conflict_stats,
2236 ctx, scratch_pool));
2238 return SVN_NO_ERROR;