| /* |
| * conflict-callbacks.c: conflict resolution callbacks specific to the |
| * commandline client. |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include <apr_xlate.h> /* for APR_LOCALE_CHARSET */ |
| |
| #define APR_WANT_STRFUNC |
| #include <apr_want.h> |
| |
| #include "svn_hash.h" |
| #include "svn_cmdline.h" |
| #include "svn_client.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_types.h" |
| #include "svn_pools.h" |
| #include "svn_sorts.h" |
| #include "svn_utf.h" |
| |
| #include "cl.h" |
| #include "cl-conflicts.h" |
| |
| #include "private/svn_cmdline_private.h" |
| |
| #include "svn_private_config.h" |
| |
| #define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0]))) |
| |
| |
| |
| svn_cl__accept_t |
| svn_cl__accept_from_word(const char *word) |
| { |
| /* Shorthand options are consistent with svn_cl__conflict_handler(). */ |
| if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0 |
| || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0) |
| return svn_cl__accept_postpone; |
| if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0) |
| /* ### shorthand? */ |
| return svn_cl__accept_base; |
| if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0) |
| /* ### shorthand? */ |
| return svn_cl__accept_working; |
| if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0 |
| || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0) |
| return svn_cl__accept_mine_conflict; |
| if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0 |
| || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0) |
| return svn_cl__accept_theirs_conflict; |
| if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0 |
| || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0) |
| return svn_cl__accept_mine_full; |
| if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0 |
| || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0) |
| return svn_cl__accept_theirs_full; |
| if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0 |
| || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0) |
| return svn_cl__accept_edit; |
| if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0 |
| || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0) |
| return svn_cl__accept_launch; |
| if (strcmp(word, SVN_CL__ACCEPT_RECOMMENDED) == 0 |
| || strcmp(word, "r") == 0) |
| return svn_cl__accept_recommended; |
| /* word is an invalid action. */ |
| return svn_cl__accept_invalid; |
| } |
| |
| |
| /* Print on stdout a diff that shows incoming conflicting changes |
| * corresponding to the conflict described in CONFLICT. */ |
| static svn_error_t * |
| show_diff(svn_client_conflict_t *conflict, |
| const char *merged_abspath, |
| const char *path_prefix, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| const char *path1, *path2; |
| const char *label1, *label2; |
| svn_diff_t *diff; |
| svn_stream_t *output; |
| svn_diff_file_options_t *options; |
| const char *my_abspath; |
| const char *their_abspath; |
| |
| SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, NULL, |
| &their_abspath, |
| conflict, pool, pool)); |
| if (merged_abspath) |
| { |
| /* For conflicts recorded by the 'merge' operation, show a diff between |
| * 'mine' (the working version of the file as it appeared before the |
| * 'merge' operation was run) and 'merged' (the version of the file |
| * as it appears after the merge operation). |
| * |
| * For conflicts recorded by the 'update' and 'switch' operations, |
| * show a diff between 'theirs' (the new pristine version of the |
| * file) and 'merged' (the version of the file as it appears with |
| * local changes merged with the new pristine version). |
| * |
| * This way, the diff is always minimal and clearly identifies changes |
| * brought into the working copy by the update/switch/merge operation. */ |
| if (svn_client_conflict_get_operation(conflict) == svn_wc_operation_merge) |
| { |
| path1 = my_abspath; |
| label1 = _("MINE"); |
| } |
| else |
| { |
| path1 = their_abspath; |
| label1 = _("THEIRS"); |
| } |
| path2 = merged_abspath; |
| label2 = _("MERGED"); |
| } |
| else |
| { |
| /* There's no merged file, but we can show the |
| difference between mine and theirs. */ |
| path1 = their_abspath; |
| label1 = _("THEIRS"); |
| path2 = my_abspath; |
| label2 = _("MINE"); |
| } |
| |
| label1 = apr_psprintf(pool, "%s\t- %s", |
| svn_cl__local_style_skip_ancestor( |
| path_prefix, path1, pool), label1); |
| label2 = apr_psprintf(pool, "%s\t- %s", |
| svn_cl__local_style_skip_ancestor( |
| path_prefix, path2, pool), label2); |
| |
| options = svn_diff_file_options_create(pool); |
| options->ignore_eol_style = TRUE; |
| SVN_ERR(svn_stream_for_stdout(&output, pool)); |
| SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2, |
| options, pool)); |
| return svn_diff_file_output_unified4(output, diff, |
| path1, path2, |
| label1, label2, |
| APR_LOCALE_CHARSET, |
| NULL, |
| options->show_c_function, |
| options->context_size, |
| cancel_func, cancel_baton, |
| pool); |
| } |
| |
| |
| /* Print on stdout just the conflict hunks of a diff among the 'base', 'their' |
| * and 'my' files of CONFLICT. */ |
| static svn_error_t * |
| show_conflicts(svn_client_conflict_t *conflict, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| svn_diff_t *diff; |
| svn_stream_t *output; |
| svn_diff_file_options_t *options; |
| const char *base_abspath; |
| const char *my_abspath; |
| const char *their_abspath; |
| |
| SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, |
| &base_abspath, &their_abspath, |
| conflict, pool, pool)); |
| options = svn_diff_file_options_create(pool); |
| options->ignore_eol_style = TRUE; |
| SVN_ERR(svn_stream_for_stdout(&output, pool)); |
| SVN_ERR(svn_diff_file_diff3_2(&diff, base_abspath, my_abspath, their_abspath, |
| options, pool)); |
| /* ### Consider putting the markers/labels from |
| ### svn_wc__merge_internal in the conflict description. */ |
| return svn_diff_file_output_merge3( |
| output, diff, base_abspath, my_abspath, their_abspath, |
| _("||||||| ORIGINAL"), |
| _("<<<<<<< MINE (select with 'mc')"), |
| _(">>>>>>> THEIRS (select with 'tc')"), |
| "=======", |
| svn_diff_conflict_display_only_conflicts, |
| cancel_func, |
| cancel_baton, |
| pool); |
| } |
| |
| /* Perform a 3-way merge of the conflicting values of a property, |
| * and write the result to the OUTPUT stream. |
| * |
| * If MERGED_PROPVAL is non-NULL, use it as 'my' version instead of |
| * MY_ABSPATH. |
| * |
| * Assume the values are printable UTF-8 text. |
| */ |
| static svn_error_t * |
| merge_prop_conflict(svn_stream_t *output, |
| const svn_string_t *base_propval, |
| const svn_string_t *my_propval, |
| const svn_string_t *their_propval, |
| const svn_string_t *merged_propval, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| svn_diff_file_options_t *options = svn_diff_file_options_create(pool); |
| svn_diff_t *diff; |
| |
| /* If any of the property values is missing, use an empty value instead |
| * for the purpose of showing a diff. */ |
| if (base_propval == NULL) |
| base_propval = svn_string_create_empty(pool); |
| if (my_propval == NULL) |
| my_propval = svn_string_create_empty(pool); |
| if (their_propval == NULL) |
| their_propval = svn_string_create_empty(pool); |
| |
| options->ignore_eol_style = TRUE; |
| SVN_ERR(svn_diff_mem_string_diff3(&diff, base_propval, |
| merged_propval ? |
| merged_propval : my_propval, |
| their_propval, options, pool)); |
| SVN_ERR(svn_diff_mem_string_output_merge3( |
| output, diff, base_propval, |
| merged_propval ? merged_propval : my_propval, their_propval, |
| _("||||||| ORIGINAL"), |
| _("<<<<<<< MINE"), |
| _(">>>>>>> THEIRS"), |
| "=======", |
| svn_diff_conflict_display_modified_original_latest, |
| cancel_func, |
| cancel_baton, |
| pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Display the conflicting values of a property as a 3-way diff. |
| * |
| * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of |
| * DESC->MY_ABSPATH. |
| * |
| * Assume the values are printable UTF-8 text. |
| */ |
| static svn_error_t * |
| show_prop_conflict(const svn_string_t *base_propval, |
| const svn_string_t *my_propval, |
| const svn_string_t *their_propval, |
| const svn_string_t *merged_propval, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| svn_stream_t *output; |
| |
| SVN_ERR(svn_stream_for_stdout(&output, pool)); |
| SVN_ERR(merge_prop_conflict(output, base_propval, my_propval, their_propval, |
| merged_propval, cancel_func, cancel_baton, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Run an external editor, passing it the MERGED_ABSPATH, or, if the |
| * 'merged' file is null, return an error. The tool to use is determined by |
| * B->editor_cmd, B->config and environment variables; see |
| * svn_cl__edit_file_externally() for details. |
| * |
| * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not |
| * configured or cannot run, do not touch *PERFORMED_EDIT, report the error |
| * on stderr, and return SVN_NO_ERROR; if any other error is encountered, |
| * return that error. */ |
| static svn_error_t * |
| open_editor(svn_boolean_t *performed_edit, |
| const char *merged_abspath, |
| const char *editor_cmd, |
| apr_hash_t *config, |
| apr_pool_t *pool) |
| { |
| svn_error_t *err; |
| |
| if (merged_abspath) |
| { |
| err = svn_cmdline__edit_file_externally(merged_abspath, editor_cmd, |
| config, pool); |
| if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR || |
| err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) |
| { |
| char buf[1024]; |
| const char *message; |
| |
| message = svn_err_best_message(err, buf, sizeof(buf)); |
| SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", message)); |
| svn_error_clear(err); |
| } |
| else if (err) |
| return svn_error_trace(err); |
| else |
| *performed_edit = TRUE; |
| } |
| else |
| SVN_ERR(svn_cmdline_fprintf(stderr, pool, |
| _("Invalid option; there's no " |
| "merged version to edit.\n\n"))); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Run an external editor on the merged property value with conflict markers. |
| * Return the edited result in *MERGED_PROPVAL. |
| * If the edit is aborted, set *MERGED_ABSPATH and *MERGED_PROPVAL to NULL. |
| * The tool to use is determined by B->editor_cmd, B->config and |
| * environment variables; see svn_cl__edit_file_externally() for details. */ |
| static svn_error_t * |
| edit_prop_conflict(const svn_string_t **merged_propval, |
| const svn_string_t *base_propval, |
| const svn_string_t *my_propval, |
| const svn_string_t *their_propval, |
| const char *editor_cmd, |
| apr_hash_t *config, |
| svn_cmdline_prompt_baton_t *pb, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *file_path; |
| svn_boolean_t performed_edit = FALSE; |
| svn_stream_t *merged_prop; |
| |
| SVN_ERR(svn_stream_open_unique(&merged_prop, &file_path, NULL, |
| svn_io_file_del_on_pool_cleanup, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(merge_prop_conflict(merged_prop, base_propval, my_propval, |
| their_propval, NULL, |
| pb->cancel_func, |
| pb->cancel_baton, |
| scratch_pool)); |
| SVN_ERR(svn_stream_close(merged_prop)); |
| SVN_ERR(open_editor(&performed_edit, file_path, editor_cmd, |
| config, scratch_pool)); |
| if (performed_edit && merged_propval) |
| { |
| svn_stringbuf_t *buf; |
| |
| SVN_ERR(svn_stringbuf_from_file2(&buf, file_path, scratch_pool)); |
| *merged_propval = svn_string_create_from_buf(buf, result_pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Maximum line length for the prompt string. */ |
| #define MAX_PROMPT_WIDTH 70 |
| |
| /* Description of a resolver option. |
| * Resolver options are used to build the resolver's conflict prompt. |
| * The user types a code to select the corresponding conflict resolution option. |
| * Some resolver options have a corresponding --accept argument. */ |
| typedef struct resolver_option_t |
| { |
| const char *code; /* one or two characters */ |
| svn_client_conflict_option_id_t choice; |
| /* or ..._undefined if not from libsvn_client */ |
| const char *accept_arg; /* --accept option argument (NOT localized) */ |
| } resolver_option_t; |
| |
| typedef struct client_option_t |
| { |
| const char *code; /* one or two characters */ |
| const char *label; /* label in prompt (localized) */ |
| const char *long_desc; /* longer description (localized) */ |
| svn_client_conflict_option_id_t choice; |
| /* or ..._undefined if not from libsvn_client */ |
| const char *accept_arg; /* --accept option argument (NOT localized) */ |
| svn_boolean_t is_recommended; /* if TRUE, try this option before prompting */ |
| } client_option_t; |
| |
| /* Resolver options for conflict options offered by libsvn_client. */ |
| static const resolver_option_t builtin_resolver_options[] = |
| { |
| { "r", svn_client_conflict_option_merged_text, |
| SVN_CL__ACCEPT_WORKING }, |
| { "mc", svn_client_conflict_option_working_text_where_conflicted, |
| SVN_CL__ACCEPT_MINE_CONFLICT }, |
| { "tc", svn_client_conflict_option_incoming_text_where_conflicted, |
| SVN_CL__ACCEPT_THEIRS_CONFLICT }, |
| { "mf", svn_client_conflict_option_working_text, |
| SVN_CL__ACCEPT_MINE_FULL}, |
| { "tf", svn_client_conflict_option_incoming_text, |
| SVN_CL__ACCEPT_THEIRS_FULL }, |
| { "p", svn_client_conflict_option_postpone, |
| SVN_CL__ACCEPT_POSTPONE }, |
| |
| /* This option resolves a tree conflict to the current working copy state. */ |
| { "r", svn_client_conflict_option_accept_current_wc_state, |
| SVN_CL__ACCEPT_WORKING }, |
| |
| /* These options use the same code since they only occur in |
| * distinct conflict scenarios. */ |
| { "u", svn_client_conflict_option_update_move_destination }, |
| { "u", svn_client_conflict_option_update_any_moved_away_children }, |
| |
| /* Options for incoming add vs local add. */ |
| { "i", svn_client_conflict_option_incoming_add_ignore }, |
| |
| /* Options for incoming file add vs local file add upon merge. */ |
| { "m", svn_client_conflict_option_incoming_added_file_text_merge }, |
| { "M", svn_client_conflict_option_incoming_added_file_replace_and_merge }, |
| |
| /* Options for incoming dir add vs local dir add upon merge. */ |
| { "m", svn_client_conflict_option_incoming_added_dir_merge }, |
| { "R", svn_client_conflict_option_incoming_added_dir_replace }, |
| { "M", svn_client_conflict_option_incoming_added_dir_replace_and_merge }, |
| |
| /* Options for incoming delete vs any. */ |
| { "i", svn_client_conflict_option_incoming_delete_ignore }, |
| { "a", svn_client_conflict_option_incoming_delete_accept }, |
| |
| /* Options for incoming move vs local edit. */ |
| { "m", svn_client_conflict_option_incoming_move_file_text_merge }, |
| { "m", svn_client_conflict_option_incoming_move_dir_merge }, |
| |
| /* Options for local move vs incoming edit. */ |
| { "m", svn_client_conflict_option_local_move_file_text_merge }, |
| { "m", svn_client_conflict_option_local_move_dir_merge }, |
| |
| /* Options for local missing vs incoming edit. */ |
| { "m", svn_client_conflict_option_sibling_move_file_text_merge }, |
| { "m", svn_client_conflict_option_sibling_move_dir_merge }, |
| |
| /* Options for incoming move vs local move. */ |
| { "m", svn_client_conflict_option_both_moved_file_merge }, |
| { "M", svn_client_conflict_option_both_moved_file_move_merge }, |
| { "m", svn_client_conflict_option_both_moved_dir_merge }, |
| { "M", svn_client_conflict_option_both_moved_dir_move_merge }, |
| |
| { NULL } |
| }; |
| |
| /* Extra resolver options offered by 'svn' for any conflict. */ |
| static const client_option_t extra_resolver_options[] = |
| { |
| /* Translators: keep long_desc below 70 characters (wrap with a left |
| margin of 9 spaces if needed) */ |
| { "q", N_("Quit resolution"), N_("postpone all remaining conflicts"), |
| svn_client_conflict_option_postpone }, |
| { NULL } |
| }; |
| |
| |
| /* Additional resolver options offered by 'svn' for a text conflict. */ |
| static const client_option_t extra_resolver_options_text[] = |
| { |
| /* Translators: keep long_desc below 70 characters (wrap with a left |
| margin of 9 spaces if needed) */ |
| { "e", N_("Edit file"), N_("change merged file in an editor"), |
| svn_client_conflict_option_undefined, |
| SVN_CL__ACCEPT_EDIT }, |
| { "df", N_("Show diff"), N_("show all changes made to merged file"), |
| svn_client_conflict_option_undefined}, |
| { "dc", N_("Display conflict"), N_("show all conflicts " |
| "(ignoring merged version)"), |
| svn_client_conflict_option_undefined }, |
| { "m", N_("Merge"), N_("use merge tool to resolve conflict"), |
| svn_client_conflict_option_undefined }, |
| { "l", N_("Launch tool"), N_("launch external merge tool to resolve " |
| "conflict"), |
| svn_client_conflict_option_undefined, |
| SVN_CL__ACCEPT_LAUNCH }, |
| { "i", N_("Internal merge tool"), N_("use built-in merge tool to " |
| "resolve conflict"), |
| svn_client_conflict_option_undefined }, |
| { "s", N_("Show all options"), N_("show this list (also 'h', '?')"), |
| svn_client_conflict_option_undefined }, |
| { NULL } |
| }; |
| |
| /* Additional resolver options offered by 'svn' for a property conflict. */ |
| static const client_option_t extra_resolver_options_prop[] = |
| { |
| /* Translators: keep long_desc below 70 characters (wrap with a left |
| margin of 9 spaces if needed) */ |
| { "dc", N_("Display conflict"), N_("show conflicts in this property"), |
| svn_client_conflict_option_undefined }, |
| { "e", N_("Edit property"), N_("change merged property value in an " |
| "editor"), |
| svn_client_conflict_option_undefined, |
| SVN_CL__ACCEPT_EDIT }, |
| { "h", N_("Help"), N_("show this help (also '?')"), |
| svn_client_conflict_option_undefined }, |
| { NULL } |
| }; |
| |
| /* Additional resolver options offered by 'svn' for a tree conflict. */ |
| static const client_option_t extra_resolver_options_tree[] = |
| { |
| /* Translators: keep long_desc below 70 characters (wrap with a left |
| margin of 9 spaces if needed) */ |
| { "d", N_("Set repository move destination path"), |
| N_("pick repository move target from list of possible targets"), |
| svn_client_conflict_option_undefined }, |
| |
| { "w", N_("Set working copy move destination path"), |
| N_("pick working copy move target from list of possible targets"), |
| svn_client_conflict_option_undefined }, |
| |
| { "h", N_("Help"), N_("show this help (also '?')"), |
| svn_client_conflict_option_undefined }, |
| |
| { NULL } |
| }; |
| |
| |
| /* Return a pointer to the option description in OPTIONS matching the |
| * one- or two-character OPTION_CODE. Return NULL if not found. */ |
| static const client_option_t * |
| find_option(const apr_array_header_t *options, |
| const char *option_code) |
| { |
| int i; |
| |
| for (i = 0; i < options->nelts; i++) |
| { |
| const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *); |
| |
| /* Ignore code "" (blank lines) which is not a valid answer. */ |
| if (opt->code[0] && strcmp(opt->code, option_code) == 0) |
| return opt; |
| } |
| return NULL; |
| } |
| |
| /* Find the first recommended option in OPTIONS. */ |
| static const client_option_t * |
| find_recommended_option(const apr_array_header_t *options) |
| { |
| int i; |
| |
| for (i = 0; i < options->nelts; i++) |
| { |
| const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *); |
| |
| /* Ignore code "" (blank lines) which is not a valid answer. */ |
| if (opt->code[0] && opt->is_recommended) |
| return opt; |
| } |
| return NULL; |
| } |
| |
| /* Return a pointer to the client_option_t in OPTIONS matching the ID of |
| * conflict option BUILTIN_OPTION. @a out will be set to NULL if the |
| * option was not found. */ |
| static svn_error_t * |
| find_option_by_builtin(client_option_t **out, |
| svn_client_conflict_t *conflict, |
| const resolver_option_t *options, |
| svn_client_conflict_option_t *builtin_option, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const resolver_option_t *opt; |
| svn_client_conflict_option_id_t id; |
| svn_client_conflict_option_id_t recommended_id; |
| |
| id = svn_client_conflict_option_get_id(builtin_option); |
| recommended_id = svn_client_conflict_get_recommended_option_id(conflict); |
| |
| for (opt = options; opt->code; opt++) |
| { |
| if (opt->choice == id) |
| { |
| client_option_t *client_opt; |
| |
| client_opt = apr_pcalloc(result_pool, sizeof(*client_opt)); |
| client_opt->choice = id; |
| client_opt->code = opt->code; |
| client_opt->label = svn_client_conflict_option_get_label( |
| builtin_option, |
| result_pool); |
| client_opt->long_desc = svn_client_conflict_option_get_description( |
| builtin_option, |
| result_pool); |
| client_opt->accept_arg = opt->accept_arg; |
| client_opt->is_recommended = |
| (recommended_id != svn_client_conflict_option_unspecified && |
| id == recommended_id); |
| |
| *out = client_opt; |
| |
| return SVN_NO_ERROR; |
| } |
| } |
| |
| *out = NULL; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Return a prompt string listing the options OPTIONS. If OPTION_CODES is |
| * non-null, select only the options whose codes are mentioned in it. */ |
| static const char * |
| prompt_string(const apr_array_header_t *options, |
| const char *const *option_codes, |
| apr_pool_t *pool) |
| { |
| const char *result = _("Select:"); |
| int left_margin = svn_utf_cstring_utf8_width(result); |
| const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, ""); |
| int this_line_len = left_margin; |
| svn_boolean_t first = TRUE; |
| int i = 0; |
| |
| while (1) |
| { |
| const client_option_t *opt; |
| const char *s; |
| int slen; |
| |
| if (option_codes) |
| { |
| if (! *option_codes) |
| break; |
| opt = find_option(options, *option_codes++); |
| if (opt == NULL) |
| continue; |
| } |
| else |
| { |
| if (i >= options->nelts) |
| break; |
| opt = APR_ARRAY_IDX(options, i, client_option_t *); |
| i++; |
| } |
| |
| if (! first) |
| result = apr_pstrcat(pool, result, ",", SVN_VA_NULL); |
| s = apr_psprintf(pool, " (%s) %s", opt->code, |
| opt->label ? opt->label : opt->long_desc); |
| slen = svn_utf_cstring_utf8_width(s); |
| /* Break the line if adding the next option would make it too long */ |
| if (this_line_len + slen > MAX_PROMPT_WIDTH) |
| { |
| result = apr_pstrcat(pool, result, line_sep, SVN_VA_NULL); |
| this_line_len = left_margin; |
| } |
| result = apr_pstrcat(pool, result, s, SVN_VA_NULL); |
| this_line_len += slen; |
| first = FALSE; |
| } |
| return apr_pstrcat(pool, result, ": ", SVN_VA_NULL); |
| } |
| |
| /* Return a help string listing the OPTIONS. */ |
| static svn_error_t * |
| help_string(const char **result, |
| const apr_array_header_t *options, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *iterpool; |
| int i; |
| |
| *result = ""; |
| iterpool = svn_pool_create(pool); |
| for (i = 0; i < options->nelts; i++) |
| { |
| const client_option_t *opt; |
| svn_pool_clear(iterpool); |
| |
| opt = APR_ARRAY_IDX(options, i, |
| client_option_t *); |
| |
| /* Append a line describing OPT, or a blank line if its code is "". */ |
| if (opt->code[0]) |
| { |
| const char *s = apr_psprintf(pool, " (%s)", opt->code); |
| |
| if (opt->accept_arg) |
| *result = apr_psprintf(pool, "%s%-6s - %s [%s]\n", |
| *result, s, opt->long_desc, |
| opt->accept_arg); |
| else |
| *result = apr_psprintf(pool, "%s%-6s - %s\n", *result, s, |
| opt->long_desc); |
| } |
| else |
| { |
| *result = apr_pstrcat(pool, *result, "\n", SVN_VA_NULL); |
| } |
| } |
| svn_pool_destroy(iterpool); |
| *result = apr_pstrcat(pool, *result, |
| _("Words in square brackets are the corresponding " |
| "--accept option arguments.\n"), |
| SVN_VA_NULL); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed |
| * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen |
| * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to |
| * NULL if the answer was not one of them. |
| * |
| * If the answer is the (globally recognized) 'help' option, then display |
| * CONFLICT_DESCRIPTION (if not NULL) and help (on stderr) and return with |
| * *OPT == NULL. |
| */ |
| static svn_error_t * |
| prompt_user(const client_option_t **opt, |
| const apr_array_header_t *conflict_options, |
| const char *const *options_to_show, |
| const char *conflict_description, |
| void *prompt_baton, |
| apr_pool_t *scratch_pool) |
| { |
| const char *prompt |
| = prompt_string(conflict_options, options_to_show, scratch_pool); |
| const char *answer; |
| |
| SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool)); |
| if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0) |
| { |
| const char *helpstr; |
| |
| if (conflict_description) |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", |
| conflict_description)); |
| SVN_ERR(help_string(&helpstr, conflict_options, scratch_pool)); |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", helpstr)); |
| *opt = NULL; |
| } |
| else |
| { |
| *opt = find_option(conflict_options, answer); |
| if (! *opt) |
| { |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, |
| _("Unrecognized option.\n\n"))); |
| } |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| /* Set *OPTIONS to an array of resolution options for CONFLICT. */ |
| static svn_error_t * |
| build_text_conflict_options(apr_array_header_t **options, |
| svn_client_conflict_t *conflict, |
| svn_client_ctx_t *ctx, |
| svn_boolean_t is_binary, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const client_option_t *o; |
| apr_array_header_t *builtin_options; |
| int nopt; |
| int i; |
| apr_pool_t *iterpool; |
| |
| SVN_ERR(svn_client_conflict_text_get_resolution_options(&builtin_options, |
| conflict, ctx, |
| scratch_pool, |
| scratch_pool)); |
| nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options); |
| if (!is_binary) |
| nopt += ARRAY_LEN(extra_resolver_options_text); |
| *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *)); |
| |
| iterpool = svn_pool_create(scratch_pool); |
| for (i = 0; i < builtin_options->nelts; i++) |
| { |
| client_option_t *opt; |
| svn_client_conflict_option_t *builtin_option; |
| |
| svn_pool_clear(iterpool); |
| builtin_option = APR_ARRAY_IDX(builtin_options, i, |
| svn_client_conflict_option_t *); |
| SVN_ERR(find_option_by_builtin(&opt, conflict, |
| builtin_resolver_options, |
| builtin_option, |
| result_pool, |
| iterpool)); |
| if (opt == NULL) |
| continue; /* ### unknown option -- assign a code dynamically? */ |
| |
| APR_ARRAY_PUSH(*options, client_option_t *) = opt; |
| } |
| |
| for (o = extra_resolver_options; o->code; o++) |
| APR_ARRAY_PUSH(*options, const client_option_t *) = o; |
| if (!is_binary) |
| { |
| for (o = extra_resolver_options_text; o->code; o++) |
| APR_ARRAY_PUSH(*options, const client_option_t *) = o; |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Mark CONFLICT as resolved to resolution option with ID OPTION_ID. |
| * If TEXT_CONFLICTED is true, resolve text conflicts described by CONFLICT. |
| * IF PROPNAME is not NULL, mark the conflict in the specified property as |
| * resolved. If PROPNAME is "", mark all property conflicts described by |
| * CONFLICT as resolved. |
| * If TREE_CONFLICTED is true, resolve tree conflicts described by CONFLICT. |
| * Adjust CONFLICT_STATS as necessary (PATH_PREFIX is needed for this step). */ |
| static svn_error_t * |
| mark_conflict_resolved(svn_client_conflict_t *conflict, |
| svn_client_conflict_option_id_t option_id, |
| svn_boolean_t text_conflicted, |
| const char *propname, |
| svn_boolean_t tree_conflicted, |
| const char *path_prefix, |
| svn_cl__conflict_stats_t *conflict_stats, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool) |
| { |
| const char *local_relpath |
| = svn_cl__local_style_skip_ancestor( |
| path_prefix, svn_client_conflict_get_local_abspath(conflict), |
| scratch_pool); |
| |
| if (text_conflicted) |
| { |
| SVN_ERR(svn_client_conflict_text_resolve_by_id(conflict, option_id, |
| ctx, scratch_pool)); |
| svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, |
| svn_wc_conflict_kind_text); |
| } |
| |
| if (propname) |
| { |
| SVN_ERR(svn_client_conflict_prop_resolve_by_id(conflict, propname, |
| option_id, ctx, |
| scratch_pool)); |
| svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, |
| svn_wc_conflict_kind_property); |
| } |
| |
| if (tree_conflicted) |
| { |
| SVN_ERR(svn_client_conflict_tree_resolve_by_id(conflict, option_id, |
| ctx, scratch_pool)); |
| svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, |
| svn_wc_conflict_kind_tree); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Ask the user what to do about the text conflict described by CONFLICT |
| * and either resolve the conflict accordingly or postpone resolution. |
| * SCRATCH_POOL is used for temporary allocations. */ |
| static svn_error_t * |
| handle_text_conflict(svn_boolean_t *resolved, |
| svn_boolean_t *postponed, |
| svn_boolean_t *quit, |
| svn_boolean_t *printed_description, |
| svn_client_conflict_t *conflict, |
| const char *path_prefix, |
| svn_cmdline_prompt_baton_t *pb, |
| const char *editor_cmd, |
| apr_hash_t *config, |
| svn_cl__conflict_stats_t *conflict_stats, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| svn_boolean_t diff_allowed = FALSE; |
| /* Have they done something that might have affected the merged file? */ |
| svn_boolean_t performed_edit = FALSE; |
| /* Have they done *something* (edit, look at diff, etc) to |
| give them a rational basis for choosing (r)esolved? */ |
| svn_boolean_t knows_something = FALSE; |
| const char *local_relpath; |
| const char *local_abspath = svn_client_conflict_get_local_abspath(conflict); |
| const char *mime_type = svn_client_conflict_text_get_mime_type(conflict); |
| svn_boolean_t is_binary = mime_type ? svn_mime_type_is_binary(mime_type) |
| : FALSE; |
| const char *base_abspath; |
| const char *my_abspath; |
| const char *their_abspath; |
| const char *merged_abspath = svn_client_conflict_get_local_abspath(conflict); |
| apr_array_header_t *text_conflict_options; |
| svn_client_conflict_option_id_t option_id; |
| |
| option_id = svn_client_conflict_option_unspecified; |
| |
| SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, |
| &base_abspath, &their_abspath, |
| conflict, scratch_pool, |
| scratch_pool)); |
| |
| local_relpath = svn_cl__local_style_skip_ancestor(path_prefix, |
| local_abspath, |
| scratch_pool); |
| |
| if (!*printed_description) |
| { |
| if (is_binary) |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, |
| _("Merge conflict discovered in binary " |
| "file '%s'.\n"), |
| local_relpath)); |
| else |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, |
| _("Merge conflict discovered in file '%s'.\n"), |
| local_relpath)); |
| *printed_description = TRUE; |
| } |
| |
| /* ### TODO This whole feature availability check is grossly outdated. |
| DIFF_ALLOWED needs either to be redefined or to go away. |
| */ |
| |
| /* Diffing can happen between base and merged, to show conflict |
| markers to the user (this is the typical 3-way merge |
| scenario), or if no base is available, we can show a diff |
| between mine and theirs. */ |
| if (!is_binary && |
| ((merged_abspath && base_abspath) |
| || (!base_abspath && my_abspath && their_abspath))) |
| diff_allowed = TRUE; |
| |
| SVN_ERR(build_text_conflict_options(&text_conflict_options, conflict, ctx, |
| is_binary, scratch_pool, scratch_pool)); |
| while (TRUE) |
| { |
| const char *suggested_options[9]; /* filled statically below */ |
| const char **next_option = suggested_options; |
| const client_option_t *opt; |
| |
| svn_pool_clear(iterpool); |
| |
| *next_option++ = "p"; |
| if (diff_allowed) |
| { |
| /* We need one more path for this feature. */ |
| if (my_abspath) |
| *next_option++ = "df"; |
| |
| *next_option++ = "e"; |
| |
| /* We need one more path for this feature. */ |
| if (my_abspath) |
| *next_option++ = "m"; |
| |
| if (knows_something) |
| *next_option++ = "r"; |
| } |
| else |
| { |
| if (knows_something || is_binary) |
| *next_option++ = "r"; |
| |
| /* The 'mine-full' option selects the ".mine" file for texts or |
| * the current working directory file for binary files. */ |
| if (my_abspath || is_binary) |
| *next_option++ = "mf"; |
| |
| *next_option++ = "tf"; |
| } |
| *next_option++ = "s"; |
| *next_option++ = NULL; |
| |
| SVN_ERR(prompt_user(&opt, text_conflict_options, suggested_options, |
| NULL, pb, iterpool)); |
| if (! opt) |
| continue; |
| |
| if (strcmp(opt->code, "q") == 0) |
| { |
| option_id = opt->choice; |
| *quit = TRUE; |
| break; |
| } |
| else if (strcmp(opt->code, "s") == 0) |
| { |
| const char *helpstr; |
| |
| SVN_ERR(help_string(&helpstr, text_conflict_options, iterpool)); |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", |
| helpstr)); |
| } |
| else if (strcmp(opt->code, "dc") == 0) |
| { |
| if (is_binary) |
| { |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, |
| _("Invalid option; cannot " |
| "display conflicts for a " |
| "binary file.\n\n"))); |
| continue; |
| } |
| else if (! (my_abspath && base_abspath && their_abspath)) |
| { |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, |
| _("Invalid option; original " |
| "files not available.\n\n"))); |
| continue; |
| } |
| SVN_ERR(show_conflicts(conflict, |
| pb->cancel_func, |
| pb->cancel_baton, |
| iterpool)); |
| knows_something = TRUE; |
| } |
| else if (strcmp(opt->code, "df") == 0) |
| { |
| /* Re-check preconditions. */ |
| if (! diff_allowed || ! my_abspath) |
| { |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, |
| _("Invalid option; there's no " |
| "merged version to diff.\n\n"))); |
| continue; |
| } |
| |
| SVN_ERR(show_diff(conflict, merged_abspath, path_prefix, |
| pb->cancel_func, pb->cancel_baton, |
| iterpool)); |
| knows_something = TRUE; |
| } |
| else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0) |
| { |
| SVN_ERR(open_editor(&performed_edit, merged_abspath, editor_cmd, |
| config, iterpool)); |
| if (performed_edit) |
| knows_something = TRUE; |
| } |
| else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 || |
| strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0) |
| { |
| svn_error_t *err; |
| |
| /* Re-check preconditions. */ |
| if (! my_abspath) |
| { |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, |
| _("Invalid option; there's no " |
| "base path to merge.\n\n"))); |
| continue; |
| } |
| |
| err = svn_cl__merge_file_externally(base_abspath, |
| their_abspath, |
| my_abspath, |
| merged_abspath, |
| local_abspath, config, |
| NULL, iterpool); |
| if (err) |
| { |
| if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) |
| { |
| svn_boolean_t remains_in_conflict = TRUE; |
| |
| /* Try the internal merge tool. */ |
| svn_error_clear(err); |
| SVN_ERR(svn_cl__merge_file(&remains_in_conflict, |
| base_abspath, |
| their_abspath, |
| my_abspath, |
| merged_abspath, |
| local_abspath, |
| path_prefix, |
| editor_cmd, |
| config, |
| pb->cancel_func, |
| pb->cancel_baton, |
| iterpool)); |
| knows_something = !remains_in_conflict; |
| } |
| else if (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) |
| { |
| char buf[1024]; |
| const char *message; |
| |
| message = svn_err_best_message(err, buf, sizeof(buf)); |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, |
| "%s\n", message)); |
| svn_error_clear(err); |
| continue; |
| } |
| else |
| return svn_error_trace(err); |
| } |
| else |
| { |
| /* The external merge tool's exit code was either 0 or 1. |
| * The tool may leave the file conflicted by exiting with |
| * exit code 1, and we allow the user to mark the conflict |
| * resolved in this case. */ |
| performed_edit = TRUE; |
| knows_something = TRUE; |
| } |
| } |
| else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0) |
| { |
| /* ### This check should be earlier as it's nasty to offer an option |
| * and then when the user chooses it say 'Invalid option'. */ |
| /* ### 'merged_abspath' shouldn't be necessary *before* we launch the |
| * resolver: it should be the *result* of doing so. */ |
| if (base_abspath && their_abspath && my_abspath && merged_abspath) |
| { |
| svn_error_t *err; |
| char buf[1024]; |
| const char *message; |
| |
| err = svn_cl__merge_file_externally(base_abspath, |
| their_abspath, |
| my_abspath, |
| merged_abspath, |
| local_abspath, |
| config, NULL, iterpool); |
| if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL || |
| err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) |
| { |
| message = svn_err_best_message(err, buf, sizeof(buf)); |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", |
| message)); |
| svn_error_clear(err); |
| } |
| else if (err) |
| return svn_error_trace(err); |
| else |
| performed_edit = TRUE; |
| |
| if (performed_edit) |
| knows_something = TRUE; |
| } |
| else |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, |
| _("Invalid option.\n\n"))); |
| } |
| else if (strcmp(opt->code, "i") == 0) |
| { |
| svn_boolean_t remains_in_conflict = TRUE; |
| |
| SVN_ERR(svn_cl__merge_file(&remains_in_conflict, |
| base_abspath, |
| their_abspath, |
| my_abspath, |
| merged_abspath, |
| local_abspath, |
| path_prefix, |
| editor_cmd, |
| config, |
| pb->cancel_func, |
| pb->cancel_baton, |
| iterpool)); |
| |
| if (!remains_in_conflict) |
| knows_something = TRUE; |
| } |
| else if (opt->choice != svn_client_conflict_option_undefined) |
| { |
| if ((opt->choice == svn_client_conflict_option_working_text_where_conflicted |
| || opt->choice == svn_client_conflict_option_incoming_text_where_conflicted) |
| && is_binary) |
| { |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, |
| _("Invalid option; cannot choose " |
| "based on conflicts in a " |
| "binary file.\n\n"))); |
| continue; |
| } |
| |
| /* We only allow the user accept the merged version of |
| the file if they've edited it, or at least looked at |
| the diff. */ |
| if (opt->choice == svn_client_conflict_option_merged_text |
| && ! knows_something && diff_allowed) |
| { |
| SVN_ERR(svn_cmdline_fprintf( |
| stderr, iterpool, |
| _("Invalid option; use diff/edit/merge/launch " |
| "before choosing 'mark resolved'.\n\n"))); |
| continue; |
| } |
| |
| option_id = opt->choice; |
| break; |
| } |
| } |
| svn_pool_destroy(iterpool); |
| |
| if (option_id != svn_client_conflict_option_unspecified && |
| option_id != svn_client_conflict_option_postpone) |
| { |
| SVN_ERR(mark_conflict_resolved(conflict, option_id, |
| TRUE, NULL, FALSE, |
| path_prefix, conflict_stats, |
| ctx, scratch_pool)); |
| *resolved = TRUE; |
| } |
| else |
| { |
| *resolved = FALSE; |
| *postponed = (option_id == svn_client_conflict_option_postpone); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Set *OPTIONS to an array of resolution options for CONFLICT. */ |
| static svn_error_t * |
| build_prop_conflict_options(apr_array_header_t **options, |
| svn_client_conflict_t *conflict, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const client_option_t *o; |
| apr_array_header_t *builtin_options; |
| int nopt; |
| int i; |
| apr_pool_t *iterpool; |
| |
| SVN_ERR(svn_client_conflict_prop_get_resolution_options(&builtin_options, |
| conflict, ctx, |
| scratch_pool, |
| scratch_pool)); |
| nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options) + |
| ARRAY_LEN(extra_resolver_options_prop); |
| *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *)); |
| |
| iterpool = svn_pool_create(scratch_pool); |
| for (i = 0; i < builtin_options->nelts; i++) |
| { |
| client_option_t *opt; |
| svn_client_conflict_option_t *builtin_option; |
| |
| svn_pool_clear(iterpool); |
| builtin_option = APR_ARRAY_IDX(builtin_options, i, |
| svn_client_conflict_option_t *); |
| SVN_ERR(find_option_by_builtin(&opt, conflict, |
| builtin_resolver_options, |
| builtin_option, |
| result_pool, |
| iterpool)); |
| if (opt == NULL) |
| continue; /* ### unknown option -- assign a code dynamically? */ |
| |
| APR_ARRAY_PUSH(*options, client_option_t *) = opt; |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| for (o = extra_resolver_options; o->code; o++) |
| APR_ARRAY_PUSH(*options, const client_option_t *) = o; |
| for (o = extra_resolver_options_prop; o->code; o++) |
| APR_ARRAY_PUSH(*options, const client_option_t *) = o; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Ask the user what to do about the conflicted property PROPNAME described |
| * by CONFLICT and return the corresponding resolution option in *OPTION. |
| * SCRATCH_POOL is used for temporary allocations. */ |
| static svn_error_t * |
| handle_one_prop_conflict(svn_client_conflict_option_t **option, |
| svn_boolean_t *quit, |
| const char *path_prefix, |
| svn_cmdline_prompt_baton_t *pb, |
| const char *editor_cmd, |
| apr_hash_t *config, |
| svn_client_conflict_t *conflict, |
| const char *propname, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *iterpool; |
| const char *description; |
| const svn_string_t *merged_propval = NULL; |
| svn_boolean_t resolved_allowed = FALSE; |
| const svn_string_t *base_propval; |
| const svn_string_t *my_propval; |
| const svn_string_t *their_propval; |
| apr_array_header_t *resolution_options; |
| apr_array_header_t *prop_conflict_options; |
| |
| SVN_ERR(svn_client_conflict_prop_get_propvals(NULL, &my_propval, |
| &base_propval, &their_propval, |
| conflict, propname, |
| scratch_pool)); |
| |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, |
| _("Conflict for property '%s' discovered" |
| " on '%s'.\n"), |
| propname, |
| svn_cl__local_style_skip_ancestor( |
| path_prefix, |
| svn_client_conflict_get_local_abspath(conflict), |
| scratch_pool))); |
| SVN_ERR(svn_client_conflict_prop_get_description(&description, conflict, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", description)); |
| |
| SVN_ERR(svn_client_conflict_prop_get_resolution_options(&resolution_options, |
| conflict, ctx, |
| result_pool, |
| scratch_pool)); |
| SVN_ERR(build_prop_conflict_options(&prop_conflict_options, conflict, ctx, |
| scratch_pool, scratch_pool)); |
| iterpool = svn_pool_create(scratch_pool); |
| while (TRUE) |
| { |
| const client_option_t *opt; |
| const char *suggested_options[9]; /* filled statically below */ |
| const char **next_option = suggested_options; |
| |
| *next_option++ = "p"; |
| *next_option++ = "mf"; |
| *next_option++ = "tf"; |
| *next_option++ = "dc"; |
| *next_option++ = "e"; |
| if (resolved_allowed) |
| *next_option++ = "r"; |
| *next_option++ = "q"; |
| *next_option++ = "h"; |
| *next_option++ = NULL; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(prompt_user(&opt, prop_conflict_options, suggested_options, |
| NULL, pb, iterpool)); |
| if (! opt) |
| continue; |
| |
| if (strcmp(opt->code, "q") == 0) |
| { |
| *option = svn_client_conflict_option_find_by_id(resolution_options, |
| opt->choice); |
| *quit = TRUE; |
| break; |
| } |
| else if (strcmp(opt->code, "dc") == 0) |
| { |
| SVN_ERR(show_prop_conflict(base_propval, my_propval, their_propval, |
| merged_propval, |
| pb->cancel_func, pb->cancel_baton, |
| scratch_pool)); |
| } |
| else if (strcmp(opt->code, "e") == 0) |
| { |
| SVN_ERR(edit_prop_conflict(&merged_propval, |
| base_propval, my_propval, their_propval, |
| editor_cmd, config, pb, |
| result_pool, scratch_pool)); |
| resolved_allowed = (merged_propval != NULL); |
| } |
| else if (strcmp(opt->code, "r") == 0) |
| { |
| if (! resolved_allowed) |
| { |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, |
| _("Invalid option; please edit the property " |
| "first.\n\n"))); |
| continue; |
| } |
| |
| *option = svn_client_conflict_option_find_by_id( |
| resolution_options, |
| svn_client_conflict_option_merged_text); |
| svn_client_conflict_option_set_merged_propval(*option, |
| merged_propval); |
| break; |
| } |
| else if (opt->choice != svn_client_conflict_option_undefined) |
| { |
| *option = svn_client_conflict_option_find_by_id(resolution_options, |
| opt->choice); |
| break; |
| } |
| } |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Ask the user what to do about the property conflicts described by CONFLICT |
| * and either resolve them accordingly or postpone resolution. |
| * SCRATCH_POOL is used for temporary allocations. */ |
| static svn_error_t * |
| handle_prop_conflicts(svn_boolean_t *resolved, |
| svn_boolean_t *postponed, |
| svn_boolean_t *quit, |
| const svn_string_t **merged_value, |
| const char *path_prefix, |
| svn_cmdline_prompt_baton_t *pb, |
| const char *editor_cmd, |
| apr_hash_t *config, |
| svn_client_conflict_t *conflict, |
| svn_cl__conflict_stats_t *conflict_stats, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_array_header_t *props_conflicted; |
| apr_pool_t *iterpool; |
| int i; |
| int nresolved = 0; |
| |
| SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL, |
| conflict, scratch_pool, |
| scratch_pool)); |
| |
| iterpool = svn_pool_create(scratch_pool); |
| for (i = 0; i < props_conflicted->nelts; i++) |
| { |
| const char *propname = APR_ARRAY_IDX(props_conflicted, i, const char *); |
| svn_client_conflict_option_t *option; |
| svn_client_conflict_option_id_t option_id; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(handle_one_prop_conflict(&option, quit, path_prefix, pb, |
| editor_cmd, config, conflict, propname, |
| ctx, |
| iterpool, iterpool)); |
| option_id = svn_client_conflict_option_get_id(option); |
| |
| if (option_id != svn_client_conflict_option_unspecified && |
| option_id != svn_client_conflict_option_postpone) |
| { |
| const char *local_relpath = |
| svn_cl__local_style_skip_ancestor( |
| path_prefix, svn_client_conflict_get_local_abspath(conflict), |
| iterpool); |
| |
| SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option, |
| ctx, iterpool)); |
| svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, |
| svn_wc_conflict_kind_property); |
| nresolved++; |
| *postponed = FALSE; |
| } |
| else |
| *postponed = (option_id == svn_client_conflict_option_postpone); |
| |
| if (*quit) |
| break; |
| } |
| svn_pool_destroy(iterpool); |
| |
| /* Indicate success if no property conflicts remain. */ |
| *resolved = (nresolved == props_conflicted->nelts); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Set *OPTIONS to an array of resolution options for CONFLICT. */ |
| static svn_error_t * |
| build_tree_conflict_options( |
| apr_array_header_t **options, |
| apr_array_header_t **possible_moved_to_repos_relpaths, |
| apr_array_header_t **possible_moved_to_abspaths, |
| svn_boolean_t *all_options_are_dumb, |
| svn_client_conflict_t *conflict, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const client_option_t *o; |
| apr_array_header_t *builtin_options; |
| int nopt; |
| int i; |
| int next_unknown_option_code = 1; |
| apr_pool_t *iterpool; |
| |
| if (all_options_are_dumb != NULL) |
| *all_options_are_dumb = TRUE; |
| |
| SVN_ERR(svn_client_conflict_tree_get_resolution_options(&builtin_options, |
| conflict, ctx, |
| scratch_pool, |
| scratch_pool)); |
| nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options_tree) + |
| ARRAY_LEN(extra_resolver_options); |
| *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *)); |
| *possible_moved_to_abspaths = NULL; |
| *possible_moved_to_repos_relpaths = NULL; |
| |
| iterpool = svn_pool_create(scratch_pool); |
| for (i = 0; i < builtin_options->nelts; i++) |
| { |
| client_option_t *opt; |
| svn_client_conflict_option_t *builtin_option; |
| svn_client_conflict_option_id_t id; |
| |
| svn_pool_clear(iterpool); |
| builtin_option = APR_ARRAY_IDX(builtin_options, i, |
| svn_client_conflict_option_t *); |
| SVN_ERR(find_option_by_builtin(&opt, conflict, |
| builtin_resolver_options, |
| builtin_option, |
| result_pool, |
| iterpool)); |
| if (opt == NULL) |
| { |
| /* Unkown option. Assign a dynamic option code. */ |
| opt = apr_pcalloc(result_pool, sizeof(*opt)); |
| opt->code = apr_psprintf(result_pool, "%d", next_unknown_option_code); |
| next_unknown_option_code++; |
| opt->label = svn_client_conflict_option_get_label(builtin_option, |
| result_pool); |
| opt->long_desc = svn_client_conflict_option_get_description( |
| builtin_option, result_pool); |
| opt->choice = svn_client_conflict_option_get_id(builtin_option); |
| opt->accept_arg = NULL; |
| } |
| |
| APR_ARRAY_PUSH(*options, client_option_t *) = opt; |
| |
| id = svn_client_conflict_option_get_id(builtin_option); |
| |
| /* Check if we got a "smart" tree conflict option. */ |
| if (all_options_are_dumb != NULL && |
| *all_options_are_dumb && |
| id != svn_client_conflict_option_postpone && |
| id != svn_client_conflict_option_accept_current_wc_state) |
| *all_options_are_dumb = FALSE; |
| |
| if (*possible_moved_to_repos_relpaths == NULL) |
| SVN_ERR( |
| svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( |
| possible_moved_to_repos_relpaths, builtin_option, |
| result_pool, iterpool)); |
| |
| if (*possible_moved_to_abspaths == NULL) |
| SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2( |
| possible_moved_to_abspaths, builtin_option, |
| result_pool, iterpool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| for (o = extra_resolver_options_tree; o->code; o++) |
| { |
| /* Add move target choice options only if there are multiple |
| * move targets to choose from. */ |
| if (strcmp(o->code, "d") == 0 && |
| (*possible_moved_to_repos_relpaths == NULL || |
| (*possible_moved_to_repos_relpaths)->nelts <= 1)) |
| continue; |
| if (strcmp(o->code, "w") == 0 && |
| (*possible_moved_to_abspaths == NULL || |
| (*possible_moved_to_abspaths)->nelts <= 1)) |
| continue; |
| |
| APR_ARRAY_PUSH(*options, const client_option_t *) = o; |
| } |
| for (o = extra_resolver_options; o->code; o++) |
| APR_ARRAY_PUSH(*options, const client_option_t *) = o; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Make the user select a move target path for the moved-away VICTIM_ABSPATH. */ |
| static svn_error_t * |
| prompt_move_target_path(int *preferred_move_target_idx, |
| apr_array_header_t *possible_moved_to_paths, |
| svn_boolean_t paths_are_local, |
| svn_cmdline_prompt_baton_t *pb, |
| const char *victim_abspath, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool) |
| { |
| const char *move_targets_prompt = ""; |
| const char *move_targets_list = ""; |
| const char *wcroot_abspath; |
| const char *victim_relpath; |
| int i; |
| apr_int64_t idx; |
| apr_pool_t *iterpool; |
| |
| SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, victim_abspath, |
| ctx, scratch_pool, scratch_pool)); |
| victim_relpath = svn_cl__local_style_skip_ancestor(wcroot_abspath, |
| victim_abspath, |
| scratch_pool), |
| iterpool = svn_pool_create(scratch_pool); |
| |
| /* Build the prompt. */ |
| for (i = 0; i < possible_moved_to_paths->nelts; i++) |
| { |
| svn_pool_clear(iterpool); |
| |
| if (paths_are_local) |
| { |
| const char *moved_to_abspath; |
| const char *moved_to_relpath; |
| |
| moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_paths, i, |
| const char *); |
| moved_to_relpath = svn_cl__local_style_skip_ancestor( |
| wcroot_abspath, moved_to_abspath, iterpool), |
| move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '%s'\n", |
| move_targets_list, i + 1, |
| moved_to_relpath); |
| } |
| else |
| { |
| const char *moved_to_repos_relpath; |
| |
| moved_to_repos_relpath = APR_ARRAY_IDX(possible_moved_to_paths, i, |
| const char *); |
| move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '^/%s'\n", |
| move_targets_list, i + 1, |
| moved_to_repos_relpath); |
| } |
| } |
| if (paths_are_local) |
| move_targets_prompt = |
| apr_psprintf(scratch_pool, |
| _("Possible working copy destinations for moved-away '%s' " |
| "are:\n%s" |
| "Only one destination can be a move; the others are " |
| "copies.\n" |
| "Specify the correct move target path by number: "), |
| victim_relpath, move_targets_list); |
| else |
| move_targets_prompt = |
| apr_psprintf(scratch_pool, |
| _("Possible repository destinations for moved-away '%s' " |
| "are:\n%s" |
| "Only one destination can be a move; the others are " |
| "copies.\n" |
| "Specify the correct move target path by number: "), |
| victim_relpath, move_targets_list); |
| |
| /* Keep asking the user until we got a valid choice. */ |
| while (1) |
| { |
| const char *answer; |
| svn_error_t *err; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(svn_cmdline_prompt_user2(&answer, move_targets_prompt, |
| pb, iterpool)); |
| err = svn_cstring_strtoi64(&idx, answer, 1, |
| possible_moved_to_paths->nelts, 10); |
| if (err) |
| { |
| char buf[1024]; |
| |
| SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", |
| svn_err_best_message(err, buf, sizeof(buf)))); |
| svn_error_clear(err); |
| continue; |
| } |
| |
| break; |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| SVN_ERR_ASSERT((idx - 1) == (int)(idx - 1)); |
| *preferred_move_target_idx = (int)(idx - 1); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| find_conflict_option_with_repos_move_targets( |
| svn_client_conflict_option_t **option_with_move_targets, |
| apr_array_header_t *options, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| int i; |
| apr_array_header_t *possible_moved_to_repos_relpaths = NULL; |
| |
| *option_with_move_targets = NULL; |
| |
| for (i = 0; i < options->nelts; i++) |
| { |
| svn_client_conflict_option_t *option; |
| |
| svn_pool_clear(iterpool); |
| option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); |
| SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( |
| &possible_moved_to_repos_relpaths, option, iterpool, iterpool)); |
| if (possible_moved_to_repos_relpaths) |
| { |
| *option_with_move_targets = option; |
| break; |
| } |
| } |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| find_conflict_option_with_working_copy_move_targets( |
| svn_client_conflict_option_t **option_with_move_targets, |
| apr_array_header_t *options, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| int i; |
| apr_array_header_t *possible_moved_to_abspaths = NULL; |
| |
| *option_with_move_targets = NULL; |
| |
| for (i = 0; i < options->nelts; i++) |
| { |
| svn_client_conflict_option_t *option; |
| |
| svn_pool_clear(iterpool); |
| option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); |
| SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2( |
| &possible_moved_to_abspaths, option, scratch_pool, |
| iterpool)); |
| if (possible_moved_to_abspaths) |
| { |
| *option_with_move_targets = option; |
| break; |
| } |
| } |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Ask the user what to do about the tree conflict described by CONFLICT |
| * and either resolve the conflict accordingly or postpone resolution. |
| * SCRATCH_POOL is used for temporary allocations. */ |
| static svn_error_t * |
| handle_tree_conflict(svn_boolean_t *resolved, |
| svn_boolean_t *postponed, |
| svn_boolean_t *quit, |
| svn_boolean_t *printed_description, |
| svn_client_conflict_t *conflict, |
| const char *path_prefix, |
| svn_cmdline_prompt_baton_t *pb, |
| svn_cl__conflict_stats_t *conflict_stats, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *iterpool; |
| apr_array_header_t *tree_conflict_options; |
| svn_client_conflict_option_id_t option_id; |
| const char *local_abspath; |
| const char *conflict_description; |
| const char *local_change_description; |
| const char *incoming_change_description; |
| apr_array_header_t *possible_moved_to_repos_relpaths; |
| apr_array_header_t *possible_moved_to_abspaths; |
| svn_boolean_t all_options_are_dumb; |
| const struct client_option_t *recommended_option; |
| svn_boolean_t repos_move_target_chosen = FALSE; |
| svn_boolean_t wc_move_target_chosen = FALSE; |
| |
| option_id = svn_client_conflict_option_unspecified; |
| local_abspath = svn_client_conflict_get_local_abspath(conflict); |
| |
| /* Always show the best possible conflict description and options. */ |
| SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, scratch_pool)); |
| |
| SVN_ERR(svn_client_conflict_tree_get_description( |
| &incoming_change_description, &local_change_description, |
| conflict, ctx, scratch_pool, scratch_pool)); |
| conflict_description = apr_psprintf(scratch_pool, "%s\n%s", |
| incoming_change_description, |
| local_change_description); |
| if (!*printed_description) |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, |
| _("Tree conflict on '%s':\n%s\n"), |
| svn_cl__local_style_skip_ancestor( |
| path_prefix, local_abspath, scratch_pool), |
| conflict_description)); |
| |
| SVN_ERR(build_tree_conflict_options(&tree_conflict_options, |
| &possible_moved_to_repos_relpaths, |
| &possible_moved_to_abspaths, |
| &all_options_are_dumb, |
| conflict, ctx, |
| scratch_pool, scratch_pool)); |
| |
| /* Try a recommended resolution option before prompting. */ |
| recommended_option = find_recommended_option(tree_conflict_options); |
| if (recommended_option) |
| { |
| svn_error_t *err; |
| apr_status_t root_cause; |
| |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _("Applying recommended resolution '%s':\n"), |
| recommended_option->label)); |
| |
| err = mark_conflict_resolved(conflict, recommended_option->choice, |
| FALSE, NULL, TRUE, |
| path_prefix, conflict_stats, |
| ctx, scratch_pool); |
| if (!err) |
| { |
| *resolved = TRUE; |
| return SVN_NO_ERROR; |
| } |
| |
| root_cause = svn_error_root_cause(err)->apr_err; |
| if (root_cause != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE && |
| root_cause != SVN_ERR_WC_OBSTRUCTED_UPDATE && |
| root_cause != SVN_ERR_WC_FOUND_CONFLICT) |
| return svn_error_trace(err); |
| |
| /* Fall back to interactive prompting. */ |
| svn_error_clear(err); |
| } |
| |
| if (all_options_are_dumb) |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, |
| _("\nSubversion is not smart enough to resolve " |
| "this tree conflict automatically!\nSee 'svn " |
| "help resolve' for more information.\n\n"))); |
| |
| iterpool = svn_pool_create(scratch_pool); |
| while (1) |
| { |
| const client_option_t *opt; |
| |
| svn_pool_clear(iterpool); |
| |
| if (!repos_move_target_chosen && |
| possible_moved_to_repos_relpaths && |
| possible_moved_to_repos_relpaths->nelts > 1) |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _("Ambiguous move destinations exist in the repository; " |
| "try the 'd' option\n"))); |
| if (!wc_move_target_chosen && possible_moved_to_abspaths && |
| possible_moved_to_abspaths->nelts > 1) |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _("Ambiguous move destinations exist in the working copy; " |
| "try the 'w' option\n"))); |
| |
| SVN_ERR(prompt_user(&opt, tree_conflict_options, NULL, |
| conflict_description, pb, iterpool)); |
| *printed_description = TRUE; |
| if (! opt) |
| continue; |
| |
| if (strcmp(opt->code, "q") == 0) |
| { |
| option_id = opt->choice; |
| *quit = TRUE; |
| break; |
| } |
| else if (strcmp(opt->code, "d") == 0) |
| { |
| int preferred_move_target_idx; |
| apr_array_header_t *options; |
| svn_client_conflict_option_t *option; |
| |
| SVN_ERR(prompt_move_target_path(&preferred_move_target_idx, |
| possible_moved_to_repos_relpaths, |
| FALSE, |
| pb, local_abspath, ctx, iterpool)); |
| |
| /* Update preferred move target path. */ |
| SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, |
| conflict, |
| ctx, |
| iterpool, |
| iterpool)); |
| SVN_ERR(find_conflict_option_with_repos_move_targets( |
| &option, options, iterpool)); |
| if (option) |
| { |
| SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath2( |
| option, preferred_move_target_idx, ctx, iterpool)); |
| repos_move_target_chosen = TRUE; |
| wc_move_target_chosen = FALSE; |
| |
| /* Update option description. */ |
| SVN_ERR(build_tree_conflict_options( |
| &tree_conflict_options, |
| &possible_moved_to_repos_relpaths, |
| &possible_moved_to_abspaths, |
| NULL, conflict, ctx, |
| scratch_pool, scratch_pool)); |
| |
| /* Update conflict description. */ |
| SVN_ERR(svn_client_conflict_tree_get_description( |
| &incoming_change_description, &local_change_description, |
| conflict, ctx, scratch_pool, scratch_pool)); |
| conflict_description = apr_psprintf(scratch_pool, "%s\n%s", |
| incoming_change_description, |
| local_change_description); |
| } |
| continue; |
| } |
| else if (strcmp(opt->code, "w") == 0) |
| { |
| int preferred_move_target_idx; |
| apr_array_header_t *options; |
| svn_client_conflict_option_t *option; |
| |
| SVN_ERR(prompt_move_target_path(&preferred_move_target_idx, |
| possible_moved_to_abspaths, TRUE, |
| pb, local_abspath, ctx, iterpool)); |
| |
| /* Update preferred move target path. */ |
| SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, |
| conflict, |
| ctx, |
| iterpool, |
| iterpool)); |
| SVN_ERR(find_conflict_option_with_working_copy_move_targets( |
| &option, options, iterpool)); |
| if (option) |
| { |
| SVN_ERR(svn_client_conflict_option_set_moved_to_abspath2( |
| option, preferred_move_target_idx, ctx, iterpool)); |
| wc_move_target_chosen = TRUE; |
| |
| /* Update option description. */ |
| SVN_ERR(build_tree_conflict_options( |
| &tree_conflict_options, |
| &possible_moved_to_repos_relpaths, |
| &possible_moved_to_abspaths, |
| NULL, conflict, ctx, |
| scratch_pool, scratch_pool)); |
| } |
| continue; |
| } |
| else if (opt->choice != svn_client_conflict_option_undefined) |
| { |
| option_id = opt->choice; |
| break; |
| } |
| } |
| svn_pool_destroy(iterpool); |
| if (option_id != svn_client_conflict_option_unspecified && |
| option_id != svn_client_conflict_option_postpone) |
| { |
| SVN_ERR(mark_conflict_resolved(conflict, option_id, |
| FALSE, NULL, TRUE, |
| path_prefix, conflict_stats, |
| ctx, scratch_pool)); |
| *resolved = TRUE; |
| } |
| else |
| { |
| *resolved = FALSE; |
| *postponed = (option_id == svn_client_conflict_option_postpone); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| resolve_conflict_interactively(svn_boolean_t *resolved, |
| svn_boolean_t *postponed, |
| svn_boolean_t *quit, |
| svn_boolean_t *external_failed, |
| svn_boolean_t *printed_summary, |
| svn_boolean_t *printed_description, |
| svn_client_conflict_t *conflict, |
| const char *editor_cmd, |
| apr_hash_t *config, |
| const char *path_prefix, |
| svn_cmdline_prompt_baton_t *pb, |
| svn_cl__conflict_stats_t *conflict_stats, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool) |
| { |
| svn_boolean_t text_conflicted; |
| apr_array_header_t *props_conflicted; |
| svn_boolean_t tree_conflicted; |
| const svn_string_t *merged_propval; |
| |
| SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, |
| &props_conflicted, |
| &tree_conflicted, |
| conflict, |
| scratch_pool, |
| scratch_pool)); |
| |
| /* Print a summary of conflicts before starting interactive resolution */ |
| if (! *printed_summary) |
| { |
| SVN_ERR(svn_cl__print_conflict_stats(conflict_stats, scratch_pool)); |
| *printed_summary = TRUE; |
| } |
| |
| *resolved = FALSE; |
| if (text_conflicted |
| && (svn_client_conflict_get_incoming_change(conflict) == |
| svn_wc_conflict_action_edit) |
| && (svn_client_conflict_get_local_change(conflict) == |
| svn_wc_conflict_reason_edited)) |
| SVN_ERR(handle_text_conflict(resolved, postponed, quit, printed_description, |
| conflict, path_prefix, pb, editor_cmd, config, |
| conflict_stats, ctx, scratch_pool)); |
| if (props_conflicted->nelts > 0) |
| SVN_ERR(handle_prop_conflicts(resolved, postponed, quit, &merged_propval, |
| path_prefix, pb, editor_cmd, config, conflict, |
| conflict_stats, ctx, scratch_pool, scratch_pool)); |
| if (tree_conflicted) |
| SVN_ERR(handle_tree_conflict(resolved, postponed, quit, printed_description, |
| conflict, path_prefix, pb, conflict_stats, ctx, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_cl__resolve_conflict(svn_boolean_t *quit, |
| svn_boolean_t *external_failed, |
| svn_boolean_t *printed_summary, |
| svn_client_conflict_t *conflict, |
| svn_cl__accept_t accept_which, |
| const char *editor_cmd, |
| const char *path_prefix, |
| svn_cmdline_prompt_baton_t *pb, |
| svn_cl__conflict_stats_t *conflict_stats, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool) |
| { |
| svn_boolean_t text_conflicted; |
| apr_array_header_t *props_conflicted; |
| svn_boolean_t tree_conflicted; |
| const char *local_abspath; |
| svn_client_conflict_option_id_t option_id; |
| |
| SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, |
| &props_conflicted, |
| &tree_conflicted, |
| conflict, |
| scratch_pool, |
| scratch_pool)); |
| local_abspath = svn_client_conflict_get_local_abspath(conflict); |
| |
| if (accept_which == svn_cl__accept_unspecified) |
| { |
| option_id = svn_client_conflict_option_unspecified; |
| } |
| else if (accept_which == svn_cl__accept_postpone) |
| { |
| option_id = svn_client_conflict_option_postpone; |
| } |
| else if (accept_which == svn_cl__accept_base) |
| { |
| option_id = svn_client_conflict_option_base_text; |
| } |
| else if (accept_which == svn_cl__accept_working) |
| { |
| option_id = svn_client_conflict_option_merged_text; |
| |
| if (text_conflicted) |
| { |
| const char *mime_type = |
| svn_client_conflict_text_get_mime_type(conflict); |
| |
| /* There is no merged text for binary conflicts, behave as |
| * if 'mine-full' was chosen. */ |
| if (mime_type && svn_mime_type_is_binary(mime_type)) |
| option_id = svn_client_conflict_option_working_text; |
| } |
| else if (tree_conflicted) |
| { |
| /* For tree conflicts, map 'working' to 'accept current working |
| * copy state'. */ |
| option_id = svn_client_conflict_option_accept_current_wc_state; |
| } |
| } |
| else if (accept_which == svn_cl__accept_theirs_conflict) |
| { |
| option_id = svn_client_conflict_option_incoming_text_where_conflicted; |
| } |
| else if (accept_which == svn_cl__accept_mine_conflict) |
| { |
| option_id = svn_client_conflict_option_working_text_where_conflicted; |
| |
| if (tree_conflicted) |
| { |
| svn_wc_operation_t operation; |
| |
| operation = svn_client_conflict_get_operation(conflict); |
| if (operation == svn_wc_operation_update || |
| operation == svn_wc_operation_switch) |
| { |
| svn_wc_conflict_reason_t reason; |
| |
| reason = svn_client_conflict_get_local_change(conflict); |
| if (reason == svn_wc_conflict_reason_moved_away) |
| { |
| /* Map 'mine-conflict' to 'update move destination'. */ |
| option_id = |
| svn_client_conflict_option_update_move_destination; |
| } |
| else if (reason == svn_wc_conflict_reason_deleted || |
| reason == svn_wc_conflict_reason_replaced) |
| { |
| svn_wc_conflict_action_t action; |
| svn_node_kind_t node_kind; |
| |
| action = svn_client_conflict_get_incoming_change(conflict); |
| node_kind = |
| svn_client_conflict_tree_get_victim_node_kind(conflict); |
| |
| if (action == svn_wc_conflict_action_edit && |
| node_kind == svn_node_dir) |
| { |
| /* Map 'mine-conflict' to 'update any moved away |
| * children'. */ |
| option_id = |
| svn_client_conflict_option_update_any_moved_away_children; |
| } |
| } |
| } |
| } |
| } |
| else if (accept_which == svn_cl__accept_theirs_full) |
| { |
| option_id = svn_client_conflict_option_incoming_text; |
| } |
| else if (accept_which == svn_cl__accept_mine_full) |
| { |
| option_id = svn_client_conflict_option_working_text; |
| } |
| else if (accept_which == svn_cl__accept_edit) |
| { |
| option_id = svn_client_conflict_option_unspecified; |
| |
| if (local_abspath) |
| { |
| if (*external_failed) |
| { |
| option_id = svn_client_conflict_option_postpone; |
| } |
| else |
| { |
| svn_error_t *err; |
| |
| err = svn_cmdline__edit_file_externally(local_abspath, |
| editor_cmd, |
| ctx->config, |
| scratch_pool); |
| if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR || |
| err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) |
| { |
| char buf[1024]; |
| const char *message; |
| |
| message = svn_err_best_message(err, buf, sizeof(buf)); |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", |
| message)); |
| svn_error_clear(err); |
| *external_failed = TRUE; |
| } |
| else if (err) |
| return svn_error_trace(err); |
| option_id = svn_client_conflict_option_merged_text; |
| } |
| } |
| } |
| else if (accept_which == svn_cl__accept_launch) |
| { |
| const char *base_abspath = NULL; |
| const char *my_abspath = NULL; |
| const char *their_abspath = NULL; |
| |
| option_id = svn_client_conflict_option_unspecified; |
| |
| if (text_conflicted) |
| SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, |
| &base_abspath, |
| &their_abspath, |
| conflict, scratch_pool, |
| scratch_pool)); |
| |
| if (base_abspath && their_abspath && my_abspath && local_abspath) |
| { |
| if (*external_failed) |
| { |
| option_id = svn_client_conflict_option_postpone; |
| } |
| else |
| { |
| svn_boolean_t remains_in_conflict; |
| svn_error_t *err; |
| |
| err = svn_cl__merge_file_externally(base_abspath, their_abspath, |
| my_abspath, local_abspath, |
| local_abspath, ctx->config, |
| &remains_in_conflict, |
| scratch_pool); |
| if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL || |
| err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) |
| { |
| char buf[1024]; |
| const char *message; |
| |
| message = svn_err_best_message(err, buf, sizeof(buf)); |
| SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", |
| message)); |
| *external_failed = TRUE; |
| return svn_error_trace(err); |
| } |
| else if (err) |
| return svn_error_trace(err); |
| |
| if (remains_in_conflict) |
| option_id = svn_client_conflict_option_postpone; |
| else |
| option_id = svn_client_conflict_option_merged_text; |
| } |
| } |
| } |
| else if (accept_which == svn_cl__accept_recommended) |
| { |
| svn_client_conflict_option_id_t recommended_id; |
| |
| if (tree_conflicted) |
| SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, |
| scratch_pool)); |
| recommended_id = svn_client_conflict_get_recommended_option_id(conflict); |
| if (recommended_id != svn_client_conflict_option_unspecified) |
| option_id = recommended_id; |
| else |
| option_id = svn_client_conflict_option_postpone; |
| } |
| else |
| SVN_ERR_MALFUNCTION(); |
| |
| /* If we are in interactive mode and either the user gave no --accept |
| * option or the option did not apply, then prompt. */ |
| if (option_id == svn_client_conflict_option_unspecified) |
| { |
| svn_boolean_t resolved = FALSE; |
| svn_boolean_t postponed = FALSE; |
| svn_boolean_t printed_description = FALSE; |
| svn_error_t *err; |
| apr_pool_t *iterpool; |
| |
| *quit = FALSE; |
| |
| iterpool = svn_pool_create(scratch_pool); |
| while (!resolved && !postponed && !*quit) |
| { |
| svn_pool_clear(iterpool); |
| err = resolve_conflict_interactively(&resolved, &postponed, quit, |
| external_failed, |
| printed_summary, |
| &printed_description, |
| conflict, |
| editor_cmd, ctx->config, |
| path_prefix, pb, |
| conflict_stats, ctx, |
| iterpool); |
| if (err && err->apr_err == SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE) |
| { |
| /* Conflict resolution has failed. Let the user try again. |
| * It is always possible to break out of this loop with |
| * the 'quit' or 'postpone' options. */ |
| svn_handle_warning2(stderr, err, "svn: "); |
| svn_error_clear(err); |
| err = SVN_NO_ERROR; |
| } |
| SVN_ERR(err); |
| } |
| svn_pool_destroy(iterpool); |
| } |
| else if (option_id != svn_client_conflict_option_postpone) |
| SVN_ERR(mark_conflict_resolved(conflict, option_id, |
| text_conflicted, |
| props_conflicted->nelts > 0 ? "" : NULL, |
| tree_conflicted, |
| path_prefix, conflict_stats, |
| ctx, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |