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 },
443 { "m", svn_client_conflict_option_local_move_dir_merge },
445 /* Options for local missing vs incoming edit. */
446 { "m", svn_client_conflict_option_sibling_move_file_text_merge },
447 { "m", svn_client_conflict_option_sibling_move_dir_merge },
449 /* Options for incoming move vs local move. */
450 { "m", svn_client_conflict_option_both_moved_file_merge },
451 { "M", svn_client_conflict_option_both_moved_file_move_merge },
452 { "m", svn_client_conflict_option_both_moved_dir_merge },
453 { "M", svn_client_conflict_option_both_moved_dir_move_merge },
458 /* Extra resolver options offered by 'svn' for any conflict. */
459 static const client_option_t extra_resolver_options[] =
461 /* Translators: keep long_desc below 70 characters (wrap with a left
462 margin of 9 spaces if needed) */
463 { "q", N_("Quit resolution"), N_("postpone all remaining conflicts"),
464 svn_client_conflict_option_postpone },
469 /* Additional resolver options offered by 'svn' for a text conflict. */
470 static const client_option_t extra_resolver_options_text[] =
472 /* Translators: keep long_desc below 70 characters (wrap with a left
473 margin of 9 spaces if needed) */
474 { "e", N_("Edit file"), N_("change merged file in an editor"),
475 svn_client_conflict_option_undefined,
476 SVN_CL__ACCEPT_EDIT },
477 { "df", N_("Show diff"), N_("show all changes made to merged file"),
478 svn_client_conflict_option_undefined},
479 { "dc", N_("Display conflict"), N_("show all conflicts "
480 "(ignoring merged version)"),
481 svn_client_conflict_option_undefined },
482 { "m", N_("Merge"), N_("use merge tool to resolve conflict"),
483 svn_client_conflict_option_undefined },
484 { "l", N_("Launch tool"), N_("launch external merge tool to resolve "
486 svn_client_conflict_option_undefined,
487 SVN_CL__ACCEPT_LAUNCH },
488 { "i", N_("Internal merge tool"), N_("use built-in merge tool to "
490 svn_client_conflict_option_undefined },
491 { "s", N_("Show all options"), N_("show this list (also 'h', '?')"),
492 svn_client_conflict_option_undefined },
496 /* Additional resolver options offered by 'svn' for a property conflict. */
497 static const client_option_t extra_resolver_options_prop[] =
499 /* Translators: keep long_desc below 70 characters (wrap with a left
500 margin of 9 spaces if needed) */
501 { "dc", N_("Display conflict"), N_("show conflicts in this property"),
502 svn_client_conflict_option_undefined },
503 { "e", N_("Edit property"), N_("change merged property value in an "
505 svn_client_conflict_option_undefined,
506 SVN_CL__ACCEPT_EDIT },
507 { "h", N_("Help"), N_("show this help (also '?')"),
508 svn_client_conflict_option_undefined },
512 /* Additional resolver options offered by 'svn' for a tree conflict. */
513 static const client_option_t extra_resolver_options_tree[] =
515 /* Translators: keep long_desc below 70 characters (wrap with a left
516 margin of 9 spaces if needed) */
517 { "d", N_("Set repository move destination path"),
518 N_("pick repository move target from list of possible targets"),
519 svn_client_conflict_option_undefined },
521 { "w", N_("Set working copy move destination path"),
522 N_("pick working copy move target from list of possible targets"),
523 svn_client_conflict_option_undefined },
525 { "h", N_("Help"), N_("show this help (also '?')"),
526 svn_client_conflict_option_undefined },
532 /* Return a pointer to the option description in OPTIONS matching the
533 * one- or two-character OPTION_CODE. Return NULL if not found. */
534 static const client_option_t *
535 find_option(const apr_array_header_t *options,
536 const char *option_code)
540 for (i = 0; i < options->nelts; i++)
542 const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *);
544 /* Ignore code "" (blank lines) which is not a valid answer. */
545 if (opt->code[0] && strcmp(opt->code, option_code) == 0)
551 /* Find the first recommended option in OPTIONS. */
552 static const client_option_t *
553 find_recommended_option(const apr_array_header_t *options)
557 for (i = 0; i < options->nelts; i++)
559 const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *);
561 /* Ignore code "" (blank lines) which is not a valid answer. */
562 if (opt->code[0] && opt->is_recommended)
568 /* Return a pointer to the client_option_t in OPTIONS matching the ID of
569 * conflict option BUILTIN_OPTION. @a out will be set to NULL if the
570 * option was not found. */
572 find_option_by_builtin(client_option_t **out,
573 svn_client_conflict_t *conflict,
574 const resolver_option_t *options,
575 svn_client_conflict_option_t *builtin_option,
576 apr_pool_t *result_pool,
577 apr_pool_t *scratch_pool)
579 const resolver_option_t *opt;
580 svn_client_conflict_option_id_t id;
581 svn_client_conflict_option_id_t recommended_id;
583 id = svn_client_conflict_option_get_id(builtin_option);
584 recommended_id = svn_client_conflict_get_recommended_option_id(conflict);
586 for (opt = options; opt->code; opt++)
588 if (opt->choice == id)
590 client_option_t *client_opt;
592 client_opt = apr_pcalloc(result_pool, sizeof(*client_opt));
593 client_opt->choice = id;
594 client_opt->code = opt->code;
595 client_opt->label = svn_client_conflict_option_get_label(
598 client_opt->long_desc = svn_client_conflict_option_get_description(
601 client_opt->accept_arg = opt->accept_arg;
602 client_opt->is_recommended =
603 (recommended_id != svn_client_conflict_option_unspecified &&
604 id == recommended_id);
617 /* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
618 * non-null, select only the options whose codes are mentioned in it. */
620 prompt_string(const apr_array_header_t *options,
621 const char *const *option_codes,
624 const char *result = _("Select:");
625 int left_margin = svn_utf_cstring_utf8_width(result);
626 const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
627 int this_line_len = left_margin;
628 svn_boolean_t first = TRUE;
633 const client_option_t *opt;
641 opt = find_option(options, *option_codes++);
647 if (i >= options->nelts)
649 opt = APR_ARRAY_IDX(options, i, client_option_t *);
654 result = apr_pstrcat(pool, result, ",", SVN_VA_NULL);
655 s = apr_psprintf(pool, " (%s) %s", opt->code,
656 opt->label ? opt->label : opt->long_desc);
657 slen = svn_utf_cstring_utf8_width(s);
658 /* Break the line if adding the next option would make it too long */
659 if (this_line_len + slen > MAX_PROMPT_WIDTH)
661 result = apr_pstrcat(pool, result, line_sep, SVN_VA_NULL);
662 this_line_len = left_margin;
664 result = apr_pstrcat(pool, result, s, SVN_VA_NULL);
665 this_line_len += slen;
668 return apr_pstrcat(pool, result, ": ", SVN_VA_NULL);
671 /* Return a help string listing the OPTIONS. */
673 help_string(const char **result,
674 const apr_array_header_t *options,
677 apr_pool_t *iterpool;
681 iterpool = svn_pool_create(pool);
682 for (i = 0; i < options->nelts; i++)
684 const client_option_t *opt;
685 svn_pool_clear(iterpool);
687 opt = APR_ARRAY_IDX(options, i,
690 /* Append a line describing OPT, or a blank line if its code is "". */
693 const char *s = apr_psprintf(pool, " (%s)", opt->code);
696 *result = apr_psprintf(pool, "%s%-6s - %s [%s]\n",
697 *result, s, opt->long_desc,
700 *result = apr_psprintf(pool, "%s%-6s - %s\n", *result, s,
705 *result = apr_pstrcat(pool, *result, "\n", SVN_VA_NULL);
708 svn_pool_destroy(iterpool);
709 *result = apr_pstrcat(pool, *result,
710 _("Words in square brackets are the corresponding "
711 "--accept option arguments.\n"),
716 /* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
717 * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen
718 * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
719 * NULL if the answer was not one of them.
721 * If the answer is the (globally recognized) 'help' option, then display
722 * CONFLICT_DESCRIPTION (if not NULL) and help (on stderr) and return with
726 prompt_user(const client_option_t **opt,
727 const apr_array_header_t *conflict_options,
728 const char *const *options_to_show,
729 const char *conflict_description,
731 apr_pool_t *scratch_pool)
734 = prompt_string(conflict_options, options_to_show, scratch_pool);
737 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
738 if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
742 if (conflict_description)
743 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
744 conflict_description));
745 SVN_ERR(help_string(&helpstr, conflict_options, scratch_pool));
746 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", helpstr));
751 *opt = find_option(conflict_options, answer);
754 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
755 _("Unrecognized option.\n\n")));
761 /* Set *OPTIONS to an array of resolution options for CONFLICT. */
763 build_text_conflict_options(apr_array_header_t **options,
764 svn_client_conflict_t *conflict,
765 svn_client_ctx_t *ctx,
766 svn_boolean_t is_binary,
767 apr_pool_t *result_pool,
768 apr_pool_t *scratch_pool)
770 const client_option_t *o;
771 apr_array_header_t *builtin_options;
774 apr_pool_t *iterpool;
776 SVN_ERR(svn_client_conflict_text_get_resolution_options(&builtin_options,
780 nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options);
782 nopt += ARRAY_LEN(extra_resolver_options_text);
783 *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
785 iterpool = svn_pool_create(scratch_pool);
786 for (i = 0; i < builtin_options->nelts; i++)
788 client_option_t *opt;
789 svn_client_conflict_option_t *builtin_option;
791 svn_pool_clear(iterpool);
792 builtin_option = APR_ARRAY_IDX(builtin_options, i,
793 svn_client_conflict_option_t *);
794 SVN_ERR(find_option_by_builtin(&opt, conflict,
795 builtin_resolver_options,
800 continue; /* ### unknown option -- assign a code dynamically? */
802 APR_ARRAY_PUSH(*options, client_option_t *) = opt;
805 for (o = extra_resolver_options; o->code; o++)
806 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
809 for (o = extra_resolver_options_text; o->code; o++)
810 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
813 svn_pool_destroy(iterpool);
818 /* Mark CONFLICT as resolved to resolution option with ID OPTION_ID.
819 * If TEXT_CONFLICTED is true, resolve text conflicts described by CONFLICT.
820 * IF PROPNAME is not NULL, mark the conflict in the specified property as
821 * resolved. If PROPNAME is "", mark all property conflicts described by
822 * CONFLICT as resolved.
823 * If TREE_CONFLICTED is true, resolve tree conflicts described by CONFLICT.
824 * Adjust CONFLICT_STATS as necessary (PATH_PREFIX is needed for this step). */
826 mark_conflict_resolved(svn_client_conflict_t *conflict,
827 svn_client_conflict_option_id_t option_id,
828 svn_boolean_t text_conflicted,
829 const char *propname,
830 svn_boolean_t tree_conflicted,
831 const char *path_prefix,
832 svn_cl__conflict_stats_t *conflict_stats,
833 svn_client_ctx_t *ctx,
834 apr_pool_t *scratch_pool)
836 const char *local_relpath
837 = svn_cl__local_style_skip_ancestor(
838 path_prefix, svn_client_conflict_get_local_abspath(conflict),
843 SVN_ERR(svn_client_conflict_text_resolve_by_id(conflict, option_id,
845 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
846 svn_wc_conflict_kind_text);
851 SVN_ERR(svn_client_conflict_prop_resolve_by_id(conflict, propname,
854 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
855 svn_wc_conflict_kind_property);
860 SVN_ERR(svn_client_conflict_tree_resolve_by_id(conflict, option_id,
862 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
863 svn_wc_conflict_kind_tree);
869 /* Ask the user what to do about the text conflict described by CONFLICT
870 * and either resolve the conflict accordingly or postpone resolution.
871 * SCRATCH_POOL is used for temporary allocations. */
873 handle_text_conflict(svn_boolean_t *resolved,
874 svn_boolean_t *postponed,
876 svn_boolean_t *printed_description,
877 svn_client_conflict_t *conflict,
878 const char *path_prefix,
879 svn_cmdline_prompt_baton_t *pb,
880 const char *editor_cmd,
882 svn_cl__conflict_stats_t *conflict_stats,
883 svn_client_ctx_t *ctx,
884 apr_pool_t *scratch_pool)
886 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
887 svn_boolean_t diff_allowed = FALSE;
888 /* Have they done something that might have affected the merged file? */
889 svn_boolean_t performed_edit = FALSE;
890 /* Have they done *something* (edit, look at diff, etc) to
891 give them a rational basis for choosing (r)esolved? */
892 svn_boolean_t knows_something = FALSE;
893 const char *local_relpath;
894 const char *local_abspath = svn_client_conflict_get_local_abspath(conflict);
895 const char *mime_type = svn_client_conflict_text_get_mime_type(conflict);
896 svn_boolean_t is_binary = mime_type ? svn_mime_type_is_binary(mime_type)
898 const char *base_abspath;
899 const char *my_abspath;
900 const char *their_abspath;
901 const char *merged_abspath = svn_client_conflict_get_local_abspath(conflict);
902 apr_array_header_t *text_conflict_options;
903 svn_client_conflict_option_id_t option_id;
905 option_id = svn_client_conflict_option_unspecified;
907 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
908 &base_abspath, &their_abspath,
909 conflict, scratch_pool,
912 local_relpath = svn_cl__local_style_skip_ancestor(path_prefix,
916 if (!*printed_description)
919 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
920 _("Merge conflict discovered in binary "
924 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
925 _("Merge conflict discovered in file '%s'.\n"),
927 *printed_description = TRUE;
930 /* ### TODO This whole feature availability check is grossly outdated.
931 DIFF_ALLOWED needs either to be redefined or to go away.
934 /* Diffing can happen between base and merged, to show conflict
935 markers to the user (this is the typical 3-way merge
936 scenario), or if no base is available, we can show a diff
937 between mine and theirs. */
939 ((merged_abspath && base_abspath)
940 || (!base_abspath && my_abspath && their_abspath)))
943 SVN_ERR(build_text_conflict_options(&text_conflict_options, conflict, ctx,
944 is_binary, scratch_pool, scratch_pool));
947 const char *suggested_options[9]; /* filled statically below */
948 const char **next_option = suggested_options;
949 const client_option_t *opt;
951 svn_pool_clear(iterpool);
953 *next_option++ = "p";
956 /* We need one more path for this feature. */
958 *next_option++ = "df";
960 *next_option++ = "e";
962 /* We need one more path for this feature. */
964 *next_option++ = "m";
967 *next_option++ = "r";
971 if (knows_something || is_binary)
972 *next_option++ = "r";
974 /* The 'mine-full' option selects the ".mine" file for texts or
975 * the current working directory file for binary files. */
976 if (my_abspath || is_binary)
977 *next_option++ = "mf";
979 *next_option++ = "tf";
981 *next_option++ = "s";
982 *next_option++ = NULL;
984 SVN_ERR(prompt_user(&opt, text_conflict_options, suggested_options,
985 NULL, pb, iterpool));
989 if (strcmp(opt->code, "q") == 0)
991 option_id = opt->choice;
995 else if (strcmp(opt->code, "s") == 0)
999 SVN_ERR(help_string(&helpstr, text_conflict_options, iterpool));
1000 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
1003 else if (strcmp(opt->code, "dc") == 0)
1007 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1008 _("Invalid option; cannot "
1009 "display conflicts for a "
1010 "binary file.\n\n")));
1013 else if (! (my_abspath && base_abspath && their_abspath))
1015 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1016 _("Invalid option; original "
1017 "files not available.\n\n")));
1020 SVN_ERR(show_conflicts(conflict,
1024 knows_something = TRUE;
1026 else if (strcmp(opt->code, "df") == 0)
1028 /* Re-check preconditions. */
1029 if (! diff_allowed || ! my_abspath)
1031 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1032 _("Invalid option; there's no "
1033 "merged version to diff.\n\n")));
1037 SVN_ERR(show_diff(conflict, merged_abspath, path_prefix,
1038 pb->cancel_func, pb->cancel_baton,
1040 knows_something = TRUE;
1042 else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
1044 SVN_ERR(open_editor(&performed_edit, merged_abspath, editor_cmd,
1047 knows_something = TRUE;
1049 else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
1050 strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
1054 /* Re-check preconditions. */
1057 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1058 _("Invalid option; there's no "
1059 "base path to merge.\n\n")));
1063 err = svn_cl__merge_file_externally(base_abspath,
1067 local_abspath, config,
1071 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
1073 svn_boolean_t remains_in_conflict = TRUE;
1075 /* Try the internal merge tool. */
1076 svn_error_clear(err);
1077 SVN_ERR(svn_cl__merge_file(&remains_in_conflict,
1089 knows_something = !remains_in_conflict;
1091 else if (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
1094 const char *message;
1096 message = svn_err_best_message(err, buf, sizeof(buf));
1097 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1099 svn_error_clear(err);
1103 return svn_error_trace(err);
1107 /* The external merge tool's exit code was either 0 or 1.
1108 * The tool may leave the file conflicted by exiting with
1109 * exit code 1, and we allow the user to mark the conflict
1110 * resolved in this case. */
1111 performed_edit = TRUE;
1112 knows_something = TRUE;
1115 else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
1117 /* ### This check should be earlier as it's nasty to offer an option
1118 * and then when the user chooses it say 'Invalid option'. */
1119 /* ### 'merged_abspath' shouldn't be necessary *before* we launch the
1120 * resolver: it should be the *result* of doing so. */
1121 if (base_abspath && their_abspath && my_abspath && merged_abspath)
1125 const char *message;
1127 err = svn_cl__merge_file_externally(base_abspath,
1132 config, NULL, iterpool);
1133 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL ||
1134 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
1136 message = svn_err_best_message(err, buf, sizeof(buf));
1137 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n",
1139 svn_error_clear(err);
1142 return svn_error_trace(err);
1144 performed_edit = TRUE;
1147 knows_something = TRUE;
1150 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1151 _("Invalid option.\n\n")));
1153 else if (strcmp(opt->code, "i") == 0)
1155 svn_boolean_t remains_in_conflict = TRUE;
1157 SVN_ERR(svn_cl__merge_file(&remains_in_conflict,
1170 if (!remains_in_conflict)
1171 knows_something = TRUE;
1173 else if (opt->choice != svn_client_conflict_option_undefined)
1175 if ((opt->choice == svn_client_conflict_option_working_text_where_conflicted
1176 || opt->choice == svn_client_conflict_option_incoming_text_where_conflicted)
1179 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1180 _("Invalid option; cannot choose "
1181 "based on conflicts in a "
1182 "binary file.\n\n")));
1186 /* We only allow the user accept the merged version of
1187 the file if they've edited it, or at least looked at
1189 if (opt->choice == svn_client_conflict_option_merged_text
1190 && ! knows_something && diff_allowed)
1192 SVN_ERR(svn_cmdline_fprintf(
1194 _("Invalid option; use diff/edit/merge/launch "
1195 "before choosing 'mark resolved'.\n\n")));
1199 option_id = opt->choice;
1203 svn_pool_destroy(iterpool);
1205 if (option_id != svn_client_conflict_option_unspecified &&
1206 option_id != svn_client_conflict_option_postpone)
1208 SVN_ERR(mark_conflict_resolved(conflict, option_id,
1210 path_prefix, conflict_stats,
1211 ctx, scratch_pool));
1217 *postponed = (option_id == svn_client_conflict_option_postpone);
1220 return SVN_NO_ERROR;
1223 /* Set *OPTIONS to an array of resolution options for CONFLICT. */
1224 static svn_error_t *
1225 build_prop_conflict_options(apr_array_header_t **options,
1226 svn_client_conflict_t *conflict,
1227 svn_client_ctx_t *ctx,
1228 apr_pool_t *result_pool,
1229 apr_pool_t *scratch_pool)
1231 const client_option_t *o;
1232 apr_array_header_t *builtin_options;
1235 apr_pool_t *iterpool;
1237 SVN_ERR(svn_client_conflict_prop_get_resolution_options(&builtin_options,
1241 nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options) +
1242 ARRAY_LEN(extra_resolver_options_prop);
1243 *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
1245 iterpool = svn_pool_create(scratch_pool);
1246 for (i = 0; i < builtin_options->nelts; i++)
1248 client_option_t *opt;
1249 svn_client_conflict_option_t *builtin_option;
1251 svn_pool_clear(iterpool);
1252 builtin_option = APR_ARRAY_IDX(builtin_options, i,
1253 svn_client_conflict_option_t *);
1254 SVN_ERR(find_option_by_builtin(&opt, conflict,
1255 builtin_resolver_options,
1260 continue; /* ### unknown option -- assign a code dynamically? */
1262 APR_ARRAY_PUSH(*options, client_option_t *) = opt;
1265 svn_pool_destroy(iterpool);
1267 for (o = extra_resolver_options; o->code; o++)
1268 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1269 for (o = extra_resolver_options_prop; o->code; o++)
1270 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1272 return SVN_NO_ERROR;
1275 /* Ask the user what to do about the conflicted property PROPNAME described
1276 * by CONFLICT and return the corresponding resolution option in *OPTION.
1277 * SCRATCH_POOL is used for temporary allocations. */
1278 static svn_error_t *
1279 handle_one_prop_conflict(svn_client_conflict_option_t **option,
1280 svn_boolean_t *quit,
1281 const char *path_prefix,
1282 svn_cmdline_prompt_baton_t *pb,
1283 const char *editor_cmd,
1285 svn_client_conflict_t *conflict,
1286 const char *propname,
1287 svn_client_ctx_t *ctx,
1288 apr_pool_t *result_pool,
1289 apr_pool_t *scratch_pool)
1291 apr_pool_t *iterpool;
1292 const char *description;
1293 const svn_string_t *merged_propval = NULL;
1294 svn_boolean_t resolved_allowed = FALSE;
1295 const svn_string_t *base_propval;
1296 const svn_string_t *my_propval;
1297 const svn_string_t *their_propval;
1298 apr_array_header_t *resolution_options;
1299 apr_array_header_t *prop_conflict_options;
1301 SVN_ERR(svn_client_conflict_prop_get_propvals(NULL, &my_propval,
1302 &base_propval, &their_propval,
1306 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1307 _("Conflict for property '%s' discovered"
1310 svn_cl__local_style_skip_ancestor(
1312 svn_client_conflict_get_local_abspath(conflict),
1314 SVN_ERR(svn_client_conflict_prop_get_description(&description, conflict,
1315 scratch_pool, scratch_pool));
1316 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", description));
1318 SVN_ERR(svn_client_conflict_prop_get_resolution_options(&resolution_options,
1322 SVN_ERR(build_prop_conflict_options(&prop_conflict_options, conflict, ctx,
1323 scratch_pool, scratch_pool));
1324 iterpool = svn_pool_create(scratch_pool);
1327 const client_option_t *opt;
1328 const char *suggested_options[9]; /* filled statically below */
1329 const char **next_option = suggested_options;
1331 *next_option++ = "p";
1332 *next_option++ = "mf";
1333 *next_option++ = "tf";
1334 *next_option++ = "dc";
1335 *next_option++ = "e";
1336 if (resolved_allowed)
1337 *next_option++ = "r";
1338 *next_option++ = "q";
1339 *next_option++ = "h";
1340 *next_option++ = NULL;
1342 svn_pool_clear(iterpool);
1344 SVN_ERR(prompt_user(&opt, prop_conflict_options, suggested_options,
1345 NULL, pb, iterpool));
1349 if (strcmp(opt->code, "q") == 0)
1351 *option = svn_client_conflict_option_find_by_id(resolution_options,
1356 else if (strcmp(opt->code, "dc") == 0)
1358 SVN_ERR(show_prop_conflict(base_propval, my_propval, their_propval,
1360 pb->cancel_func, pb->cancel_baton,
1363 else if (strcmp(opt->code, "e") == 0)
1365 SVN_ERR(edit_prop_conflict(&merged_propval,
1366 base_propval, my_propval, their_propval,
1367 editor_cmd, config, pb,
1368 result_pool, scratch_pool));
1369 resolved_allowed = (merged_propval != NULL);
1371 else if (strcmp(opt->code, "r") == 0)
1373 if (! resolved_allowed)
1375 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1376 _("Invalid option; please edit the property "
1381 *option = svn_client_conflict_option_find_by_id(
1383 svn_client_conflict_option_merged_text);
1384 svn_client_conflict_option_set_merged_propval(*option,
1388 else if (opt->choice != svn_client_conflict_option_undefined)
1390 *option = svn_client_conflict_option_find_by_id(resolution_options,
1395 svn_pool_destroy(iterpool);
1397 return SVN_NO_ERROR;
1400 /* Ask the user what to do about the property conflicts described by CONFLICT
1401 * and either resolve them accordingly or postpone resolution.
1402 * SCRATCH_POOL is used for temporary allocations. */
1403 static svn_error_t *
1404 handle_prop_conflicts(svn_boolean_t *resolved,
1405 svn_boolean_t *postponed,
1406 svn_boolean_t *quit,
1407 const svn_string_t **merged_value,
1408 const char *path_prefix,
1409 svn_cmdline_prompt_baton_t *pb,
1410 const char *editor_cmd,
1412 svn_client_conflict_t *conflict,
1413 svn_cl__conflict_stats_t *conflict_stats,
1414 svn_client_ctx_t *ctx,
1415 apr_pool_t *result_pool,
1416 apr_pool_t *scratch_pool)
1418 apr_array_header_t *props_conflicted;
1419 apr_pool_t *iterpool;
1423 SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
1424 conflict, scratch_pool,
1427 iterpool = svn_pool_create(scratch_pool);
1428 for (i = 0; i < props_conflicted->nelts; i++)
1430 const char *propname = APR_ARRAY_IDX(props_conflicted, i, const char *);
1431 svn_client_conflict_option_t *option;
1432 svn_client_conflict_option_id_t option_id;
1434 svn_pool_clear(iterpool);
1436 SVN_ERR(handle_one_prop_conflict(&option, quit, path_prefix, pb,
1437 editor_cmd, config, conflict, propname,
1439 iterpool, iterpool));
1440 option_id = svn_client_conflict_option_get_id(option);
1442 if (option_id != svn_client_conflict_option_unspecified &&
1443 option_id != svn_client_conflict_option_postpone)
1445 const char *local_relpath =
1446 svn_cl__local_style_skip_ancestor(
1447 path_prefix, svn_client_conflict_get_local_abspath(conflict),
1450 SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option,
1452 svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
1453 svn_wc_conflict_kind_property);
1458 *postponed = (option_id == svn_client_conflict_option_postpone);
1463 svn_pool_destroy(iterpool);
1465 /* Indicate success if no property conflicts remain. */
1466 *resolved = (nresolved == props_conflicted->nelts);
1468 return SVN_NO_ERROR;
1471 /* Set *OPTIONS to an array of resolution options for CONFLICT. */
1472 static svn_error_t *
1473 build_tree_conflict_options(
1474 apr_array_header_t **options,
1475 apr_array_header_t **possible_moved_to_repos_relpaths,
1476 apr_array_header_t **possible_moved_to_abspaths,
1477 svn_boolean_t *all_options_are_dumb,
1478 svn_client_conflict_t *conflict,
1479 svn_client_ctx_t *ctx,
1480 apr_pool_t *result_pool,
1481 apr_pool_t *scratch_pool)
1483 const client_option_t *o;
1484 apr_array_header_t *builtin_options;
1487 int next_unknown_option_code = 1;
1488 apr_pool_t *iterpool;
1490 if (all_options_are_dumb != NULL)
1491 *all_options_are_dumb = TRUE;
1493 SVN_ERR(svn_client_conflict_tree_get_resolution_options(&builtin_options,
1497 nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options_tree) +
1498 ARRAY_LEN(extra_resolver_options);
1499 *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
1500 *possible_moved_to_abspaths = NULL;
1501 *possible_moved_to_repos_relpaths = NULL;
1503 iterpool = svn_pool_create(scratch_pool);
1504 for (i = 0; i < builtin_options->nelts; i++)
1506 client_option_t *opt;
1507 svn_client_conflict_option_t *builtin_option;
1508 svn_client_conflict_option_id_t id;
1510 svn_pool_clear(iterpool);
1511 builtin_option = APR_ARRAY_IDX(builtin_options, i,
1512 svn_client_conflict_option_t *);
1513 SVN_ERR(find_option_by_builtin(&opt, conflict,
1514 builtin_resolver_options,
1520 /* Unkown option. Assign a dynamic option code. */
1521 opt = apr_pcalloc(result_pool, sizeof(*opt));
1522 opt->code = apr_psprintf(result_pool, "%d", next_unknown_option_code);
1523 next_unknown_option_code++;
1524 opt->label = svn_client_conflict_option_get_label(builtin_option,
1526 opt->long_desc = svn_client_conflict_option_get_description(
1527 builtin_option, result_pool);
1528 opt->choice = svn_client_conflict_option_get_id(builtin_option);
1529 opt->accept_arg = NULL;
1532 APR_ARRAY_PUSH(*options, client_option_t *) = opt;
1534 id = svn_client_conflict_option_get_id(builtin_option);
1536 /* Check if we got a "smart" tree conflict option. */
1537 if (all_options_are_dumb != NULL &&
1538 *all_options_are_dumb &&
1539 id != svn_client_conflict_option_postpone &&
1540 id != svn_client_conflict_option_accept_current_wc_state)
1541 *all_options_are_dumb = FALSE;
1543 if (*possible_moved_to_repos_relpaths == NULL)
1545 svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(
1546 possible_moved_to_repos_relpaths, builtin_option,
1547 result_pool, iterpool));
1549 if (*possible_moved_to_abspaths == NULL)
1550 SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2(
1551 possible_moved_to_abspaths, builtin_option,
1552 result_pool, iterpool));
1555 svn_pool_destroy(iterpool);
1557 for (o = extra_resolver_options_tree; o->code; o++)
1559 /* Add move target choice options only if there are multiple
1560 * move targets to choose from. */
1561 if (strcmp(o->code, "d") == 0 &&
1562 (*possible_moved_to_repos_relpaths == NULL ||
1563 (*possible_moved_to_repos_relpaths)->nelts <= 1))
1565 if (strcmp(o->code, "w") == 0 &&
1566 (*possible_moved_to_abspaths == NULL ||
1567 (*possible_moved_to_abspaths)->nelts <= 1))
1570 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1572 for (o = extra_resolver_options; o->code; o++)
1573 APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1575 return SVN_NO_ERROR;
1578 /* Make the user select a move target path for the moved-away VICTIM_ABSPATH. */
1579 static svn_error_t *
1580 prompt_move_target_path(int *preferred_move_target_idx,
1581 apr_array_header_t *possible_moved_to_paths,
1582 svn_boolean_t paths_are_local,
1583 svn_cmdline_prompt_baton_t *pb,
1584 const char *victim_abspath,
1585 svn_client_ctx_t *ctx,
1586 apr_pool_t *scratch_pool)
1588 const char *move_targets_prompt = "";
1589 const char *move_targets_list = "";
1590 const char *wcroot_abspath;
1591 const char *victim_relpath;
1594 apr_pool_t *iterpool;
1596 SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, victim_abspath,
1597 ctx, scratch_pool, scratch_pool));
1598 victim_relpath = svn_cl__local_style_skip_ancestor(wcroot_abspath,
1601 iterpool = svn_pool_create(scratch_pool);
1603 /* Build the prompt. */
1604 for (i = 0; i < possible_moved_to_paths->nelts; i++)
1606 svn_pool_clear(iterpool);
1608 if (paths_are_local)
1610 const char *moved_to_abspath;
1611 const char *moved_to_relpath;
1613 moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_paths, i,
1615 moved_to_relpath = svn_cl__local_style_skip_ancestor(
1616 wcroot_abspath, moved_to_abspath, iterpool),
1617 move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '%s'\n",
1618 move_targets_list, i + 1,
1623 const char *moved_to_repos_relpath;
1625 moved_to_repos_relpath = APR_ARRAY_IDX(possible_moved_to_paths, i,
1627 move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '^/%s'\n",
1628 move_targets_list, i + 1,
1629 moved_to_repos_relpath);
1632 if (paths_are_local)
1633 move_targets_prompt =
1634 apr_psprintf(scratch_pool,
1635 _("Possible working copy destinations for moved-away '%s' "
1637 "Only one destination can be a move; the others are "
1639 "Specify the correct move target path by number: "),
1640 victim_relpath, move_targets_list);
1642 move_targets_prompt =
1643 apr_psprintf(scratch_pool,
1644 _("Possible repository destinations for moved-away '%s' "
1646 "Only one destination can be a move; the others are "
1648 "Specify the correct move target path by number: "),
1649 victim_relpath, move_targets_list);
1651 /* Keep asking the user until we got a valid choice. */
1657 svn_pool_clear(iterpool);
1659 SVN_ERR(svn_cmdline_prompt_user2(&answer, move_targets_prompt,
1661 err = svn_cstring_strtoi64(&idx, answer, 1,
1662 possible_moved_to_paths->nelts, 10);
1667 SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n",
1668 svn_err_best_message(err, buf, sizeof(buf))));
1669 svn_error_clear(err);
1676 svn_pool_destroy(iterpool);
1678 SVN_ERR_ASSERT((idx - 1) == (int)(idx - 1));
1679 *preferred_move_target_idx = (int)(idx - 1);
1680 return SVN_NO_ERROR;
1683 static svn_error_t *
1684 find_conflict_option_with_repos_move_targets(
1685 svn_client_conflict_option_t **option_with_move_targets,
1686 apr_array_header_t *options,
1687 apr_pool_t *scratch_pool)
1689 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1691 apr_array_header_t *possible_moved_to_repos_relpaths = NULL;
1693 *option_with_move_targets = NULL;
1695 for (i = 0; i < options->nelts; i++)
1697 svn_client_conflict_option_t *option;
1699 svn_pool_clear(iterpool);
1700 option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *);
1701 SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(
1702 &possible_moved_to_repos_relpaths, option, iterpool, iterpool));
1703 if (possible_moved_to_repos_relpaths)
1705 *option_with_move_targets = option;
1709 svn_pool_destroy(iterpool);
1711 return SVN_NO_ERROR;
1714 static svn_error_t *
1715 find_conflict_option_with_working_copy_move_targets(
1716 svn_client_conflict_option_t **option_with_move_targets,
1717 apr_array_header_t *options,
1718 apr_pool_t *scratch_pool)
1720 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1722 apr_array_header_t *possible_moved_to_abspaths = NULL;
1724 *option_with_move_targets = NULL;
1726 for (i = 0; i < options->nelts; i++)
1728 svn_client_conflict_option_t *option;
1730 svn_pool_clear(iterpool);
1731 option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *);
1732 SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2(
1733 &possible_moved_to_abspaths, option, scratch_pool,
1735 if (possible_moved_to_abspaths)
1737 *option_with_move_targets = option;
1741 svn_pool_destroy(iterpool);
1743 return SVN_NO_ERROR;
1746 /* Ask the user what to do about the tree conflict described by CONFLICT
1747 * and either resolve the conflict accordingly or postpone resolution.
1748 * SCRATCH_POOL is used for temporary allocations. */
1749 static svn_error_t *
1750 handle_tree_conflict(svn_boolean_t *resolved,
1751 svn_boolean_t *postponed,
1752 svn_boolean_t *quit,
1753 svn_boolean_t *printed_description,
1754 svn_client_conflict_t *conflict,
1755 const char *path_prefix,
1756 svn_cmdline_prompt_baton_t *pb,
1757 svn_cl__conflict_stats_t *conflict_stats,
1758 svn_client_ctx_t *ctx,
1759 apr_pool_t *scratch_pool)
1761 apr_pool_t *iterpool;
1762 apr_array_header_t *tree_conflict_options;
1763 svn_client_conflict_option_id_t option_id;
1764 const char *local_abspath;
1765 const char *conflict_description;
1766 const char *local_change_description;
1767 const char *incoming_change_description;
1768 apr_array_header_t *possible_moved_to_repos_relpaths;
1769 apr_array_header_t *possible_moved_to_abspaths;
1770 svn_boolean_t all_options_are_dumb;
1771 const struct client_option_t *recommended_option;
1772 svn_boolean_t repos_move_target_chosen = FALSE;
1773 svn_boolean_t wc_move_target_chosen = FALSE;
1775 option_id = svn_client_conflict_option_unspecified;
1776 local_abspath = svn_client_conflict_get_local_abspath(conflict);
1778 /* Always show the best possible conflict description and options. */
1779 SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, scratch_pool));
1781 SVN_ERR(svn_client_conflict_tree_get_description(
1782 &incoming_change_description, &local_change_description,
1783 conflict, ctx, scratch_pool, scratch_pool));
1784 conflict_description = apr_psprintf(scratch_pool, "%s\n%s",
1785 incoming_change_description,
1786 local_change_description);
1787 if (!*printed_description)
1788 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1789 _("Tree conflict on '%s':\n%s\n"),
1790 svn_cl__local_style_skip_ancestor(
1791 path_prefix, local_abspath, scratch_pool),
1792 conflict_description));
1794 SVN_ERR(build_tree_conflict_options(&tree_conflict_options,
1795 &possible_moved_to_repos_relpaths,
1796 &possible_moved_to_abspaths,
1797 &all_options_are_dumb,
1799 scratch_pool, scratch_pool));
1801 /* Try a recommended resolution option before prompting. */
1802 recommended_option = find_recommended_option(tree_conflict_options);
1803 if (recommended_option)
1806 apr_status_t root_cause;
1808 SVN_ERR(svn_cmdline_printf(scratch_pool,
1809 _("Applying recommended resolution '%s':\n"),
1810 recommended_option->label));
1812 err = mark_conflict_resolved(conflict, recommended_option->choice,
1814 path_prefix, conflict_stats,
1819 return SVN_NO_ERROR;
1822 root_cause = svn_error_root_cause(err)->apr_err;
1823 if (root_cause != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE &&
1824 root_cause != SVN_ERR_WC_OBSTRUCTED_UPDATE &&
1825 root_cause != SVN_ERR_WC_FOUND_CONFLICT)
1826 return svn_error_trace(err);
1828 /* Fall back to interactive prompting. */
1829 svn_error_clear(err);
1832 if (all_options_are_dumb)
1833 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1834 _("\nSubversion is not smart enough to resolve "
1835 "this tree conflict automatically!\nSee 'svn "
1836 "help resolve' for more information.\n\n")));
1838 iterpool = svn_pool_create(scratch_pool);
1841 const client_option_t *opt;
1843 svn_pool_clear(iterpool);
1845 if (!repos_move_target_chosen &&
1846 possible_moved_to_repos_relpaths &&
1847 possible_moved_to_repos_relpaths->nelts > 1)
1848 SVN_ERR(svn_cmdline_printf(scratch_pool,
1849 _("Ambiguous move destinations exist in the repository; "
1850 "try the 'd' option\n")));
1851 if (!wc_move_target_chosen && possible_moved_to_abspaths &&
1852 possible_moved_to_abspaths->nelts > 1)
1853 SVN_ERR(svn_cmdline_printf(scratch_pool,
1854 _("Ambiguous move destinations exist in the working copy; "
1855 "try the 'w' option\n")));
1857 SVN_ERR(prompt_user(&opt, tree_conflict_options, NULL,
1858 conflict_description, pb, iterpool));
1859 *printed_description = TRUE;
1863 if (strcmp(opt->code, "q") == 0)
1865 option_id = opt->choice;
1869 else if (strcmp(opt->code, "d") == 0)
1871 int preferred_move_target_idx;
1872 apr_array_header_t *options;
1873 svn_client_conflict_option_t *option;
1875 SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,
1876 possible_moved_to_repos_relpaths,
1878 pb, local_abspath, ctx, iterpool));
1880 /* Update preferred move target path. */
1881 SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options,
1886 SVN_ERR(find_conflict_option_with_repos_move_targets(
1887 &option, options, iterpool));
1890 SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath2(
1891 option, preferred_move_target_idx, ctx, iterpool));
1892 repos_move_target_chosen = TRUE;
1893 wc_move_target_chosen = FALSE;
1895 /* Update option description. */
1896 SVN_ERR(build_tree_conflict_options(
1897 &tree_conflict_options,
1898 &possible_moved_to_repos_relpaths,
1899 &possible_moved_to_abspaths,
1900 NULL, conflict, ctx,
1901 scratch_pool, scratch_pool));
1903 /* Update conflict description. */
1904 SVN_ERR(svn_client_conflict_tree_get_description(
1905 &incoming_change_description, &local_change_description,
1906 conflict, ctx, scratch_pool, scratch_pool));
1907 conflict_description = apr_psprintf(scratch_pool, "%s\n%s",
1908 incoming_change_description,
1909 local_change_description);
1913 else if (strcmp(opt->code, "w") == 0)
1915 int preferred_move_target_idx;
1916 apr_array_header_t *options;
1917 svn_client_conflict_option_t *option;
1919 SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,
1920 possible_moved_to_abspaths, TRUE,
1921 pb, local_abspath, ctx, iterpool));
1923 /* Update preferred move target path. */
1924 SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options,
1929 SVN_ERR(find_conflict_option_with_working_copy_move_targets(
1930 &option, options, iterpool));
1933 SVN_ERR(svn_client_conflict_option_set_moved_to_abspath2(
1934 option, preferred_move_target_idx, ctx, iterpool));
1935 wc_move_target_chosen = TRUE;
1937 /* Update option description. */
1938 SVN_ERR(build_tree_conflict_options(
1939 &tree_conflict_options,
1940 &possible_moved_to_repos_relpaths,
1941 &possible_moved_to_abspaths,
1942 NULL, conflict, ctx,
1943 scratch_pool, scratch_pool));
1947 else if (opt->choice != svn_client_conflict_option_undefined)
1949 option_id = opt->choice;
1953 svn_pool_destroy(iterpool);
1954 if (option_id != svn_client_conflict_option_unspecified &&
1955 option_id != svn_client_conflict_option_postpone)
1957 SVN_ERR(mark_conflict_resolved(conflict, option_id,
1959 path_prefix, conflict_stats,
1960 ctx, scratch_pool));
1966 *postponed = (option_id == svn_client_conflict_option_postpone);
1969 return SVN_NO_ERROR;
1972 static svn_error_t *
1973 resolve_conflict_interactively(svn_boolean_t *resolved,
1974 svn_boolean_t *postponed,
1975 svn_boolean_t *quit,
1976 svn_boolean_t *external_failed,
1977 svn_boolean_t *printed_summary,
1978 svn_boolean_t *printed_description,
1979 svn_client_conflict_t *conflict,
1980 const char *editor_cmd,
1982 const char *path_prefix,
1983 svn_cmdline_prompt_baton_t *pb,
1984 svn_cl__conflict_stats_t *conflict_stats,
1985 svn_client_ctx_t *ctx,
1986 apr_pool_t *scratch_pool)
1988 svn_boolean_t text_conflicted;
1989 apr_array_header_t *props_conflicted;
1990 svn_boolean_t tree_conflicted;
1991 const svn_string_t *merged_propval;
1993 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
2000 /* Print a summary of conflicts before starting interactive resolution */
2001 if (! *printed_summary)
2003 SVN_ERR(svn_cl__print_conflict_stats(conflict_stats, scratch_pool));
2004 *printed_summary = TRUE;
2009 && (svn_client_conflict_get_incoming_change(conflict) ==
2010 svn_wc_conflict_action_edit)
2011 && (svn_client_conflict_get_local_change(conflict) ==
2012 svn_wc_conflict_reason_edited))
2013 SVN_ERR(handle_text_conflict(resolved, postponed, quit, printed_description,
2014 conflict, path_prefix, pb, editor_cmd, config,
2015 conflict_stats, ctx, scratch_pool));
2016 if (props_conflicted->nelts > 0)
2017 SVN_ERR(handle_prop_conflicts(resolved, postponed, quit, &merged_propval,
2018 path_prefix, pb, editor_cmd, config, conflict,
2019 conflict_stats, ctx, scratch_pool, scratch_pool));
2020 if (tree_conflicted)
2021 SVN_ERR(handle_tree_conflict(resolved, postponed, quit, printed_description,
2022 conflict, path_prefix, pb, conflict_stats, ctx,
2025 return SVN_NO_ERROR;
2029 svn_cl__resolve_conflict(svn_boolean_t *quit,
2030 svn_boolean_t *external_failed,
2031 svn_boolean_t *printed_summary,
2032 svn_client_conflict_t *conflict,
2033 svn_cl__accept_t accept_which,
2034 const char *editor_cmd,
2035 const char *path_prefix,
2036 svn_cmdline_prompt_baton_t *pb,
2037 svn_cl__conflict_stats_t *conflict_stats,
2038 svn_client_ctx_t *ctx,
2039 apr_pool_t *scratch_pool)
2041 svn_boolean_t text_conflicted;
2042 apr_array_header_t *props_conflicted;
2043 svn_boolean_t tree_conflicted;
2044 const char *local_abspath;
2045 svn_client_conflict_option_id_t option_id;
2047 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
2053 local_abspath = svn_client_conflict_get_local_abspath(conflict);
2055 if (accept_which == svn_cl__accept_unspecified)
2057 option_id = svn_client_conflict_option_unspecified;
2059 else if (accept_which == svn_cl__accept_postpone)
2061 option_id = svn_client_conflict_option_postpone;
2063 else if (accept_which == svn_cl__accept_base)
2065 option_id = svn_client_conflict_option_base_text;
2067 else if (accept_which == svn_cl__accept_working)
2069 option_id = svn_client_conflict_option_merged_text;
2071 if (text_conflicted)
2073 const char *mime_type =
2074 svn_client_conflict_text_get_mime_type(conflict);
2076 /* There is no merged text for binary conflicts, behave as
2077 * if 'mine-full' was chosen. */
2078 if (mime_type && svn_mime_type_is_binary(mime_type))
2079 option_id = svn_client_conflict_option_working_text;
2081 else if (tree_conflicted)
2083 /* For tree conflicts, map 'working' to 'accept current working
2085 option_id = svn_client_conflict_option_accept_current_wc_state;
2088 else if (accept_which == svn_cl__accept_theirs_conflict)
2090 option_id = svn_client_conflict_option_incoming_text_where_conflicted;
2092 else if (accept_which == svn_cl__accept_mine_conflict)
2094 option_id = svn_client_conflict_option_working_text_where_conflicted;
2096 if (tree_conflicted)
2098 svn_wc_operation_t operation;
2100 operation = svn_client_conflict_get_operation(conflict);
2101 if (operation == svn_wc_operation_update ||
2102 operation == svn_wc_operation_switch)
2104 svn_wc_conflict_reason_t reason;
2106 reason = svn_client_conflict_get_local_change(conflict);
2107 if (reason == svn_wc_conflict_reason_moved_away)
2109 /* Map 'mine-conflict' to 'update move destination'. */
2111 svn_client_conflict_option_update_move_destination;
2113 else if (reason == svn_wc_conflict_reason_deleted ||
2114 reason == svn_wc_conflict_reason_replaced)
2116 svn_wc_conflict_action_t action;
2117 svn_node_kind_t node_kind;
2119 action = svn_client_conflict_get_incoming_change(conflict);
2121 svn_client_conflict_tree_get_victim_node_kind(conflict);
2123 if (action == svn_wc_conflict_action_edit &&
2124 node_kind == svn_node_dir)
2126 /* Map 'mine-conflict' to 'update any moved away
2129 svn_client_conflict_option_update_any_moved_away_children;
2135 else if (accept_which == svn_cl__accept_theirs_full)
2137 option_id = svn_client_conflict_option_incoming_text;
2139 else if (accept_which == svn_cl__accept_mine_full)
2141 option_id = svn_client_conflict_option_working_text;
2143 else if (accept_which == svn_cl__accept_edit)
2145 option_id = svn_client_conflict_option_unspecified;
2149 if (*external_failed)
2151 option_id = svn_client_conflict_option_postpone;
2157 err = svn_cmdline__edit_file_externally(local_abspath,
2161 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR ||
2162 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
2165 const char *message;
2167 message = svn_err_best_message(err, buf, sizeof(buf));
2168 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
2170 svn_error_clear(err);
2171 *external_failed = TRUE;
2174 return svn_error_trace(err);
2175 option_id = svn_client_conflict_option_merged_text;
2179 else if (accept_which == svn_cl__accept_launch)
2181 const char *base_abspath = NULL;
2182 const char *my_abspath = NULL;
2183 const char *their_abspath = NULL;
2185 option_id = svn_client_conflict_option_unspecified;
2187 if (text_conflicted)
2188 SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
2191 conflict, scratch_pool,
2194 if (base_abspath && their_abspath && my_abspath && local_abspath)
2196 if (*external_failed)
2198 option_id = svn_client_conflict_option_postpone;
2202 svn_boolean_t remains_in_conflict;
2205 err = svn_cl__merge_file_externally(base_abspath, their_abspath,
2206 my_abspath, local_abspath,
2207 local_abspath, ctx->config,
2208 &remains_in_conflict,
2210 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL ||
2211 err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
2214 const char *message;
2216 message = svn_err_best_message(err, buf, sizeof(buf));
2217 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
2219 *external_failed = TRUE;
2220 return svn_error_trace(err);
2223 return svn_error_trace(err);
2225 if (remains_in_conflict)
2226 option_id = svn_client_conflict_option_postpone;
2228 option_id = svn_client_conflict_option_merged_text;
2232 else if (accept_which == svn_cl__accept_recommended)
2234 svn_client_conflict_option_id_t recommended_id;
2236 if (tree_conflicted)
2237 SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx,
2239 recommended_id = svn_client_conflict_get_recommended_option_id(conflict);
2240 if (recommended_id != svn_client_conflict_option_unspecified)
2241 option_id = recommended_id;
2243 option_id = svn_client_conflict_option_postpone;
2246 SVN_ERR_MALFUNCTION();
2248 /* If we are in interactive mode and either the user gave no --accept
2249 * option or the option did not apply, then prompt. */
2250 if (option_id == svn_client_conflict_option_unspecified)
2252 svn_boolean_t resolved = FALSE;
2253 svn_boolean_t postponed = FALSE;
2254 svn_boolean_t printed_description = FALSE;
2256 apr_pool_t *iterpool;
2260 iterpool = svn_pool_create(scratch_pool);
2261 while (!resolved && !postponed && !*quit)
2263 svn_pool_clear(iterpool);
2264 err = resolve_conflict_interactively(&resolved, &postponed, quit,
2267 &printed_description,
2269 editor_cmd, ctx->config,
2271 conflict_stats, ctx,
2273 if (err && err->apr_err == SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE)
2275 /* Conflict resolution has failed. Let the user try again.
2276 * It is always possible to break out of this loop with
2277 * the 'quit' or 'postpone' options. */
2278 svn_handle_warning2(stderr, err, "svn: ");
2279 svn_error_clear(err);
2284 svn_pool_destroy(iterpool);
2286 else if (option_id != svn_client_conflict_option_postpone)
2287 SVN_ERR(mark_conflict_resolved(conflict, option_id,
2289 props_conflicted->nelts > 0 ? "" : NULL,
2291 path_prefix, conflict_stats,
2292 ctx, scratch_pool));
2294 return SVN_NO_ERROR;