diff options
Diffstat (limited to 'subversion/svn/conflict-callbacks.c')
-rw-r--r-- | subversion/svn/conflict-callbacks.c | 2103 |
1 files changed, 1486 insertions, 617 deletions
diff --git a/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c index a9cb39a2ade16..278fffcdb8826 100644 --- a/subversion/svn/conflict-callbacks.c +++ b/subversion/svn/conflict-callbacks.c @@ -44,53 +44,9 @@ #include "svn_private_config.h" #define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0]))) -#define MAX_ARRAY_LEN(aryx, aryz) \ - (ARRAY_LEN((aryx)) > ARRAY_LEN((aryz)) \ - ? ARRAY_LEN((aryx)) : ARRAY_LEN((aryz))) -struct svn_cl__interactive_conflict_baton_t { - svn_cl__accept_t accept_which; - apr_hash_t *config; - const char *editor_cmd; - svn_boolean_t external_failed; - svn_cmdline_prompt_baton_t *pb; - const char *path_prefix; - svn_boolean_t quit; - svn_cl__conflict_stats_t *conflict_stats; - svn_boolean_t printed_summary; -}; - -svn_error_t * -svn_cl__get_conflict_func_interactive_baton( - svn_cl__interactive_conflict_baton_t **b, - svn_cl__accept_t accept_which, - apr_hash_t *config, - const char *editor_cmd, - svn_cl__conflict_stats_t *conflict_stats, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *result_pool) -{ - svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb)); - pb->cancel_func = cancel_func; - pb->cancel_baton = cancel_baton; - - *b = apr_palloc(result_pool, sizeof(**b)); - (*b)->accept_which = accept_which; - (*b)->config = config; - (*b)->editor_cmd = editor_cmd; - (*b)->external_failed = FALSE; - (*b)->pb = pb; - SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool)); - (*b)->quit = FALSE; - (*b)->conflict_stats = conflict_stats; - (*b)->printed_summary = FALSE; - - return SVN_NO_ERROR; -} - svn_cl__accept_t svn_cl__accept_from_word(const char *word) { @@ -122,15 +78,19 @@ svn_cl__accept_from_word(const char *word) 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 DESC. */ + * corresponding to the conflict described in CONFLICT. */ static svn_error_t * -show_diff(const svn_wc_conflict_description2_t *desc, +show_diff(svn_client_conflict_t *conflict, + const char *merged_abspath, const char *path_prefix, svn_cancel_func_t cancel_func, void *cancel_baton, @@ -141,8 +101,13 @@ show_diff(const svn_wc_conflict_description2_t *desc, svn_diff_t *diff; svn_stream_t *output; svn_diff_file_options_t *options; + const char *my_abspath; + const char *their_abspath; - if (desc->merged_file) + 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 @@ -150,32 +115,32 @@ show_diff(const svn_wc_conflict_description2_t *desc, * as it appears after the merge operation). * * For conflicts recorded by the 'update' and 'switch' operations, - * show a diff beween 'theirs' (the new pristine version of the + * 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 (desc->operation == svn_wc_operation_merge) + if (svn_client_conflict_get_operation(conflict) == svn_wc_operation_merge) { - path1 = desc->my_abspath; + path1 = my_abspath; label1 = _("MINE"); } else { - path1 = desc->their_abspath; + path1 = their_abspath; label1 = _("THEIRS"); } - path2 = desc->merged_file; + path2 = merged_abspath; label2 = _("MERGED"); } else { /* There's no merged file, but we can show the difference between mine and theirs. */ - path1 = desc->their_abspath; + path1 = their_abspath; label1 = _("THEIRS"); - path2 = desc->my_abspath; + path2 = my_abspath; label2 = _("MINE"); } @@ -204,9 +169,9 @@ show_diff(const svn_wc_conflict_description2_t *desc, /* Print on stdout just the conflict hunks of a diff among the 'base', 'their' - * and 'my' files of DESC. */ + * and 'my' files of CONFLICT. */ static svn_error_t * -show_conflicts(const svn_wc_conflict_description2_t *desc, +show_conflicts(svn_client_conflict_t *conflict, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) @@ -214,89 +179,78 @@ show_conflicts(const svn_wc_conflict_description2_t *desc, 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, - desc->base_abspath, - desc->my_abspath, - desc->their_abspath, + 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, - desc->base_abspath, - desc->my_abspath, - desc->their_abspath, - _("||||||| ORIGINAL"), - _("<<<<<<< MINE (select with 'mc')"), - _(">>>>>>> THEIRS (select with 'tc')"), - "=======", - svn_diff_conflict_display_only_conflicts, - cancel_func, - cancel_baton, - pool); + 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_ABSPATH is non-NULL, use it as 'my' version instead of - * DESC->MY_ABSPATH. + * 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_wc_conflict_description2_t *desc, - const char *merged_abspath, + 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) { - const char *base_abspath = desc->base_abspath; - const char *my_abspath = desc->my_abspath; - const char *their_abspath = desc->their_abspath; 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 file instead + /* If any of the property values is missing, use an empty value instead * for the purpose of showing a diff. */ - if (! base_abspath || ! my_abspath || ! their_abspath) - { - const char *empty_file; - - SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file, - NULL, svn_io_file_del_on_pool_cleanup, - pool, pool)); - if (! base_abspath) - base_abspath = empty_file; - if (! my_abspath) - my_abspath = empty_file; - if (! their_abspath) - their_abspath = empty_file; - } - + 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_file_diff3_2(&diff, - base_abspath, - merged_abspath ? merged_abspath : my_abspath, - their_abspath, - options, pool)); - SVN_ERR(svn_diff_file_output_merge3(output, diff, - base_abspath, - merged_abspath ? merged_abspath - : my_abspath, - their_abspath, - _("||||||| ORIGINAL"), - _("<<<<<<< MINE"), - _(">>>>>>> THEIRS"), - "=======", - svn_diff_conflict_display_modified_original_latest, - cancel_func, - cancel_baton, - pool)); + 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; } @@ -309,8 +263,10 @@ merge_prop_conflict(svn_stream_t *output, * Assume the values are printable UTF-8 text. */ static svn_error_t * -show_prop_conflict(const svn_wc_conflict_description2_t *desc, - const char *merged_abspath, +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) @@ -318,13 +274,13 @@ show_prop_conflict(const svn_wc_conflict_description2_t *desc, svn_stream_t *output; SVN_ERR(svn_stream_for_stdout(&output, pool)); - SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, - cancel_func, cancel_baton, 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_FILE, or, if the +/* 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. @@ -335,16 +291,17 @@ show_prop_conflict(const svn_wc_conflict_description2_t *desc, * return that error. */ static svn_error_t * open_editor(svn_boolean_t *performed_edit, - const char *merged_file, - svn_cl__interactive_conflict_baton_t *b, + const char *merged_abspath, + const char *editor_cmd, + apr_hash_t *config, apr_pool_t *pool) { svn_error_t *err; - if (merged_file) + if (merged_abspath) { - err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd, - b->config, pool); + 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)) { @@ -368,34 +325,44 @@ open_editor(svn_boolean_t *performed_edit, return SVN_NO_ERROR; } -/* Run an external editor, passing it the 'merged' property in DESC. +/* 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 char **merged_file_path, - const svn_wc_conflict_description2_t *desc, - svn_cl__interactive_conflict_baton_t *b, +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) { - apr_file_t *file; const char *file_path; svn_boolean_t performed_edit = FALSE; svn_stream_t *merged_prop; - SVN_ERR(svn_io_open_unique_file3(&file, &file_path, NULL, - svn_io_file_del_on_pool_cleanup, - result_pool, scratch_pool)); - merged_prop = svn_stream_from_aprfile2(file, TRUE /* disown */, - scratch_pool); - SVN_ERR(merge_prop_conflict(merged_prop, desc, NULL, - b->pb->cancel_func, - b->pb->cancel_baton, + 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(svn_io_file_flush(file, scratch_pool)); - SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool)); - *merged_file_path = (performed_edit ? file_path : NULL); + 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; } @@ -403,169 +370,166 @@ edit_prop_conflict(const char **merged_file_path, /* Maximum line length for the prompt string. */ #define MAX_PROMPT_WIDTH 70 -/* Description of a resolver option */ +/* 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 */ - const char *short_desc; /* label in prompt (localized) */ - const char *long_desc; /* longer description (localized) */ - svn_wc_conflict_choice_t choice; - /* or ..._undefined if not a simple choice */ + 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; -/* Resolver options for a text conflict */ -/* (opt->code == "" causes a blank line break in help_string()) */ -static const resolver_option_t text_conflict_options[] = +typedef struct client_option_t { - /* Translators: keep long_desc below 70 characters (wrap with a left - margin of 9 spaces if needed); don't translate the words within square - brackets. */ - { "e", N_("edit file"), N_("change merged file in an editor" - " [edit]"), - svn_wc_conflict_choose_undefined }, - { "df", N_("show diff"), N_("show all changes made to merged file"), - svn_wc_conflict_choose_undefined }, - { "r", N_("mark resolved"), N_("accept merged version of file [working]"), - svn_wc_conflict_choose_merged }, - { "", "", "", svn_wc_conflict_choose_unspecified }, - { "dc", N_("display conflict"), N_("show all conflicts " - "(ignoring merged version)"), - svn_wc_conflict_choose_undefined }, - { "mc", N_("my side of conflict"), N_("accept my version for all conflicts " - "(same) [mine-conflict]"), - svn_wc_conflict_choose_mine_conflict }, - { "tc", N_("their side of conflict"), N_("accept their version for all " - "conflicts (same)" - " [theirs-conflict]"), - svn_wc_conflict_choose_theirs_conflict }, - { "", "", "", svn_wc_conflict_choose_unspecified }, - { "mf", N_("my version"), N_("accept my version of entire file (even " - "non-conflicts) [mine-full]"), - svn_wc_conflict_choose_mine_full }, - { "tf", N_("their version"), N_("accept their version of entire file " - "(same) [theirs-full]"), - svn_wc_conflict_choose_theirs_full }, - { "", "", "", svn_wc_conflict_choose_unspecified }, - { "m", N_("merge"), N_("use merge tool to resolve conflict"), - svn_wc_conflict_choose_undefined }, - { "l", N_("launch tool"), N_("launch external merge tool to resolve " - "conflict [launch]"), - svn_wc_conflict_choose_undefined }, - { "i", N_("internal merge tool"), N_("use built-in merge tool to " - "resolve conflict"), - svn_wc_conflict_choose_undefined }, - { "p", N_("postpone"), N_("mark the conflict to be resolved later" - " [postpone]"), - svn_wc_conflict_choose_postpone }, - { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), - svn_wc_conflict_choose_postpone }, - { "s", N_("show all options"), N_("show this list (also 'h', '?')"), - svn_wc_conflict_choose_undefined }, + 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 }, + { NULL } }; -/* Resolver options for a binary file conflict. */ -static const resolver_option_t binary_conflict_options[] = +/* 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); don't translate the words within square - brackets. */ - { "r", N_("mark resolved"), N_("accept the working copy version of file " - " [working]"), - svn_wc_conflict_choose_merged }, - { "tf", N_("their version"), N_("accept the incoming version of file " - " [theirs-full]"), - svn_wc_conflict_choose_theirs_full }, - { "p", N_("postpone"), N_("mark the conflict to be resolved later " - " [postpone]"), - svn_wc_conflict_choose_postpone }, - { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), - svn_wc_conflict_choose_postpone }, - { "s", N_("show all options"), N_("show this list (also 'h', '?')"), - svn_wc_conflict_choose_undefined }, + margin of 9 spaces if needed) */ + { "q", N_("Quit resolution"), N_("postpone all remaining conflicts"), + svn_client_conflict_option_postpone }, { NULL } }; -/* Resolver options for a property conflict */ -static const resolver_option_t prop_conflict_options[] = -{ - { "mf", N_("my version"), N_("accept my version of entire property (even " - "non-conflicts) [mine-full]"), - svn_wc_conflict_choose_mine_full }, - { "tf", N_("their version"), N_("accept their version of entire property " - "(same) [theirs-full]"), - svn_wc_conflict_choose_theirs_full }, - { "dc", N_("display conflict"), N_("show conflicts in this property"), - svn_wc_conflict_choose_undefined }, - { "e", N_("edit property"), N_("change merged property value in an editor" - " [edit]"), - svn_wc_conflict_choose_undefined }, - { "r", N_("mark resolved"), N_("accept edited version of property"), - svn_wc_conflict_choose_merged }, - { "p", N_("postpone"), N_("mark the conflict to be resolved later" - " [postpone]"), - svn_wc_conflict_choose_postpone }, - { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), - svn_wc_conflict_choose_postpone }, - { "h", N_("help"), N_("show this help (also '?')"), - svn_wc_conflict_choose_undefined }, - { NULL } -}; -/* Resolver options for a tree conflict */ -static const resolver_option_t tree_conflict_options[] = +/* Additional resolver options offered by 'svn' for a text conflict. */ +static const client_option_t extra_resolver_options_text[] = { - { "r", N_("mark resolved"), N_("accept current working copy state"), - svn_wc_conflict_choose_merged }, - { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), - svn_wc_conflict_choose_postpone }, - { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), - svn_wc_conflict_choose_postpone }, - { "h", N_("help"), N_("show this help (also '?')"), - svn_wc_conflict_choose_undefined }, + /* 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 } }; -static const resolver_option_t tree_conflict_options_update_moved_away[] = +/* Additional resolver options offered by 'svn' for a property conflict. */ +static const client_option_t extra_resolver_options_prop[] = { - { "mc", N_("apply update to move destination (recommended)"), - N_("apply incoming update to move destination" - " [mine-conflict]"), - svn_wc_conflict_choose_mine_conflict }, - { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), - svn_wc_conflict_choose_postpone }, - { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), - svn_wc_conflict_choose_postpone }, - { "h", N_("help"), N_("show this help (also '?')"), - svn_wc_conflict_choose_undefined }, + /* 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 } }; -static const resolver_option_t tree_conflict_options_update_edit_deleted_dir[] = +/* Additional resolver options offered by 'svn' for a tree conflict. */ +static const client_option_t extra_resolver_options_tree[] = { - { "mc", N_("prepare for updating moved-away children, if any (recommended)"), - N_("allow updating moved-away children " - "with 'svn resolve' [mine-conflict]"), - svn_wc_conflict_choose_mine_conflict }, - { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), - svn_wc_conflict_choose_postpone }, - { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), - svn_wc_conflict_choose_postpone }, - { "h", N_("help"), N_("show this help (also '?')"), - svn_wc_conflict_choose_undefined }, + /* 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 resolver_option_t * -find_option(const resolver_option_t *options, +static const client_option_t * +find_option(const apr_array_header_t *options, const char *option_code) { - const resolver_option_t *opt; + int i; - for (opt = options; opt->code; opt++) + 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; @@ -573,10 +537,76 @@ find_option(const resolver_option_t *options, 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 resolver_option_t *options, +prompt_string(const apr_array_header_t *options, const char *const *option_codes, apr_pool_t *pool) { @@ -585,10 +615,11 @@ prompt_string(const resolver_option_t *options, 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 resolver_option_t *opt; + const client_option_t *opt; const char *s; int slen; @@ -597,18 +628,21 @@ prompt_string(const resolver_option_t *options, if (! *option_codes) break; opt = find_option(options, *option_codes++); + if (opt == NULL) + continue; } else { - opt = options++; - if (! opt->code) + 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->short_desc)); + 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) @@ -624,33 +658,48 @@ prompt_string(const resolver_option_t *options, } /* Return a help string listing the OPTIONS. */ -static const char * -help_string(const resolver_option_t *options, +static svn_error_t * +help_string(const char **result, + const apr_array_header_t *options, apr_pool_t *pool) { - const char *result = ""; - const resolver_option_t *opt; + apr_pool_t *iterpool; + int i; - for (opt = options; opt->code; opt++) + *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); - result = apr_psprintf(pool, "%s%-6s - %s\n", - result, s, _(opt->long_desc)); + 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); + *result = apr_pstrcat(pool, *result, "\n", SVN_VA_NULL); } } - result = apr_pstrcat(pool, result, + svn_pool_destroy(iterpool); + *result = apr_pstrcat(pool, *result, _("Words in square brackets are the corresponding " "--accept option arguments.\n"), SVN_VA_NULL); - return result; + return SVN_NO_ERROR; } /* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed @@ -659,12 +708,14 @@ help_string(const resolver_option_t *options, * NULL if the answer was not one of them. * * If the answer is the (globally recognized) 'help' option, then display - * the help (on stderr) and return with *OPT == NULL. + * CONFLICT_DESCRIPTION (if not NULL) and help (on stderr) and return with + * *OPT == NULL. */ static svn_error_t * -prompt_user(const resolver_option_t **opt, - const resolver_option_t *conflict_options, +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) { @@ -675,9 +726,13 @@ prompt_user(const resolver_option_t **opt, SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool)); if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0) { - SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", - help_string(conflict_options, - scratch_pool))); + 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 @@ -692,41 +747,174 @@ prompt_user(const resolver_option_t **opt, return SVN_NO_ERROR; } -/* Ask the user what to do about the text conflict described by DESC. - * Return the answer in RESULT. B is the conflict baton for this - * conflict resolution session. +/* 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_wc_conflict_result_t *result, - const svn_wc_conflict_description2_t *desc, - svn_cl__interactive_conflict_baton_t *b, +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 (so that we need to save a .edited copy by setting the - result->save_merge flag)? */ + /* 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; - - SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text); - - local_relpath = svn_cl__local_style_skip_ancestor(b->path_prefix, - desc->local_abspath, - scratch_pool);; - - if (desc->is_binary) - SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, - _("Conflict discovered in binary file '%s'.\n"), - local_relpath)); - else - SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, - _("Conflict discovered in file '%s'.\n"), - 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. @@ -736,21 +924,18 @@ handle_text_conflict(svn_wc_conflict_result_t *result, 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 (!desc->is_binary && - ((desc->merged_file && desc->base_abspath) - || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))) + 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 *options[1 + MAX_ARRAY_LEN(binary_conflict_options, - text_conflict_options)]; - - const resolver_option_t *conflict_options = desc->is_binary - ? binary_conflict_options - : text_conflict_options; - const char **next_option = options; - const resolver_option_t *opt; + const char *suggested_options[9]; /* filled statically below */ + const char **next_option = suggested_options; + const client_option_t *opt; svn_pool_clear(iterpool); @@ -758,30 +943,26 @@ handle_text_conflict(svn_wc_conflict_result_t *result, if (diff_allowed) { /* We need one more path for this feature. */ - if (desc->my_abspath) + if (my_abspath) *next_option++ = "df"; *next_option++ = "e"; /* We need one more path for this feature. */ - if (desc->my_abspath) + if (my_abspath) *next_option++ = "m"; if (knows_something) *next_option++ = "r"; - - *next_option++ = "mc"; - *next_option++ = "tc"; } else { - if (knows_something || desc->is_binary) + if (knows_something || is_binary) *next_option++ = "r"; - /* The 'mine-full' option selects the ".mine" file so only offer - * it if that file exists. It does not exist for binary files, - * for example (questionable historical behaviour since 1.0). */ - if (desc->my_abspath) + /* 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"; @@ -789,26 +970,28 @@ handle_text_conflict(svn_wc_conflict_result_t *result, *next_option++ = "s"; *next_option++ = NULL; - SVN_ERR(prompt_user(&opt, conflict_options, options, b->pb, iterpool)); + SVN_ERR(prompt_user(&opt, text_conflict_options, suggested_options, + NULL, pb, iterpool)); if (! opt) continue; if (strcmp(opt->code, "q") == 0) { - result->choice = opt->choice; - b->accept_which = svn_cl__accept_postpone; - b->quit = TRUE; + 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", - help_string(conflict_options, - iterpool))); + helpstr)); } else if (strcmp(opt->code, "dc") == 0) { - if (desc->is_binary) + if (is_binary) { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, _("Invalid option; cannot " @@ -816,24 +999,23 @@ handle_text_conflict(svn_wc_conflict_result_t *result, "binary file.\n\n"))); continue; } - else if (! (desc->my_abspath && desc->base_abspath && - desc->their_abspath)) + 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(desc, - b->pb->cancel_func, - b->pb->cancel_baton, + 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 || ! desc->my_abspath) + if (! diff_allowed || ! my_abspath) { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, _("Invalid option; there's no " @@ -841,14 +1023,15 @@ handle_text_conflict(svn_wc_conflict_result_t *result, continue; } - SVN_ERR(show_diff(desc, b->path_prefix, - b->pb->cancel_func, b->pb->cancel_baton, + 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, desc->merged_file, b, iterpool)); + SVN_ERR(open_editor(&performed_edit, merged_abspath, editor_cmd, + config, iterpool)); if (performed_edit) knows_something = TRUE; } @@ -858,7 +1041,7 @@ handle_text_conflict(svn_wc_conflict_result_t *result, svn_error_t *err; /* Re-check preconditions. */ - if (! desc->my_abspath) + if (! my_abspath) { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, _("Invalid option; there's no " @@ -866,11 +1049,11 @@ handle_text_conflict(svn_wc_conflict_result_t *result, continue; } - err = svn_cl__merge_file_externally(desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, b->config, + err = svn_cl__merge_file_externally(base_abspath, + their_abspath, + my_abspath, + merged_abspath, + local_abspath, config, NULL, iterpool); if (err) { @@ -881,16 +1064,16 @@ handle_text_conflict(svn_wc_conflict_result_t *result, /* Try the internal merge tool. */ svn_error_clear(err); SVN_ERR(svn_cl__merge_file(&remains_in_conflict, - desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, - b->path_prefix, - b->editor_cmd, - b->config, - b->pb->cancel_func, - b->pb->cancel_baton, + 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; } @@ -922,21 +1105,20 @@ handle_text_conflict(svn_wc_conflict_result_t *result, { /* ### This check should be earlier as it's nasty to offer an option * and then when the user chooses it say 'Invalid option'. */ - /* ### 'merged_file' shouldn't be necessary *before* we launch the + /* ### 'merged_abspath' shouldn't be necessary *before* we launch the * resolver: it should be the *result* of doing so. */ - if (desc->base_abspath && desc->their_abspath && - desc->my_abspath && desc->merged_file) + 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(desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, - b->config, NULL, iterpool); + 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)) { @@ -962,26 +1144,26 @@ handle_text_conflict(svn_wc_conflict_result_t *result, svn_boolean_t remains_in_conflict = TRUE; SVN_ERR(svn_cl__merge_file(&remains_in_conflict, - desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, - b->path_prefix, - b->editor_cmd, - b->config, - b->pb->cancel_func, - b->pb->cancel_baton, + 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_wc_conflict_choose_undefined) + else if (opt->choice != svn_client_conflict_option_undefined) { - if ((opt->choice == svn_wc_conflict_choose_mine_conflict - || opt->choice == svn_wc_conflict_choose_theirs_conflict) - && desc->is_binary) + 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 " @@ -993,7 +1175,7 @@ handle_text_conflict(svn_wc_conflict_result_t *result, /* 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_wc_conflict_choose_merged + if (opt->choice == svn_client_conflict_option_merged_text && ! knows_something && diff_allowed) { SVN_ERR(svn_cmdline_fprintf( @@ -1003,59 +1185,137 @@ handle_text_conflict(svn_wc_conflict_result_t *result, continue; } - result->choice = opt->choice; - if (performed_edit) - result->save_merged = TRUE; + 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; } -/* Ask the user what to do about the property conflict described by DESC. - * Return the answer in RESULT. B is the conflict baton for this - * conflict resolution session. +/* 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_prop_conflict(svn_wc_conflict_result_t *result, - const svn_wc_conflict_description2_t *desc, - svn_cl__interactive_conflict_baton_t *b, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +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 *message; - const char *merged_file_path = NULL; + 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; - /* ### Work around a historical bug in the provider: the path to the - * conflict description file was put in the 'theirs' field, and - * 'theirs' was put in the 'merged' field. */ - ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file; - ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL; - - SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property); + 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"), - desc->property_name, + propname, svn_cl__local_style_skip_ancestor( - b->path_prefix, desc->local_abspath, + path_prefix, + svn_client_conflict_get_local_abspath(conflict), scratch_pool))); - - SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc, - scratch_pool)); - SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message)); - + 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 resolver_option_t *opt; - const char *options[ARRAY_LEN(prop_conflict_options)]; - const char **next_option = options; + 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"; @@ -1070,29 +1330,32 @@ handle_prop_conflict(svn_wc_conflict_result_t *result, svn_pool_clear(iterpool); - SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb, - iterpool)); + SVN_ERR(prompt_user(&opt, prop_conflict_options, suggested_options, + NULL, pb, iterpool)); if (! opt) continue; if (strcmp(opt->code, "q") == 0) { - result->choice = opt->choice; - b->accept_which = svn_cl__accept_postpone; - b->quit = TRUE; + *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(desc, merged_file_path, - b->pb->cancel_func, b->pb->cancel_baton, + 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_file_path, desc, b, + SVN_ERR(edit_prop_conflict(&merged_propval, + base_propval, my_propval, their_propval, + editor_cmd, config, pb, result_pool, scratch_pool)); - resolved_allowed = (merged_file_path != NULL); + resolved_allowed = (merged_propval != NULL); } else if (strcmp(opt->code, "r") == 0) { @@ -1104,13 +1367,17 @@ handle_prop_conflict(svn_wc_conflict_result_t *result, continue; } - result->merged_file = merged_file_path; - result->choice = svn_wc_conflict_choose_merged; + *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_wc_conflict_choose_undefined) + else if (opt->choice != svn_client_conflict_option_undefined) { - result->choice = opt->choice; + *option = svn_client_conflict_option_find_by_id(resolution_options, + opt->choice); break; } } @@ -1119,252 +1386,854 @@ handle_prop_conflict(svn_wc_conflict_result_t *result, return SVN_NO_ERROR; } -/* Ask the user what to do about the tree conflict described by DESC. - * Return the answer in RESULT. B is the conflict baton for this - * conflict resolution session. +/* 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_tree_conflict(svn_wc_conflict_result_t *result, - const svn_wc_conflict_description2_t *desc, - svn_cl__interactive_conflict_baton_t *b, - apr_pool_t *scratch_pool) +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) { - const char *readable_desc; + apr_array_header_t *props_conflicted; apr_pool_t *iterpool; + int i; + int nresolved = 0; - SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( - &readable_desc, desc, scratch_pool)); - SVN_ERR(svn_cmdline_fprintf( - stderr, scratch_pool, - _("Tree conflict on '%s'\n > %s\n"), - svn_cl__local_style_skip_ancestor(b->path_prefix, - desc->local_abspath, - scratch_pool), - readable_desc)); + SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL, + conflict, scratch_pool, + scratch_pool)); iterpool = svn_pool_create(scratch_pool); - while (1) + for (i = 0; i < props_conflicted->nelts; i++) { - const resolver_option_t *opt; - const resolver_option_t *tc_opts; + 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); - tc_opts = tree_conflict_options; + 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 (desc->operation == svn_wc_operation_update || - desc->operation == svn_wc_operation_switch) + if (option_id != svn_client_conflict_option_unspecified && + option_id != svn_client_conflict_option_postpone) { - if (desc->reason == svn_wc_conflict_reason_moved_away) - { - tc_opts = tree_conflict_options_update_moved_away; - } - else if (desc->reason == svn_wc_conflict_reason_deleted || - desc->reason == svn_wc_conflict_reason_replaced) - { - if (desc->action == svn_wc_conflict_action_edit && - desc->node_kind == svn_node_dir) - tc_opts = tree_conflict_options_update_edit_deleted_dir; - } + 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); - SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool)); - if (! opt) - continue; + if (*quit) + break; + } + svn_pool_destroy(iterpool); - if (strcmp(opt->code, "q") == 0) + /* 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) { - result->choice = opt->choice; - b->accept_which = svn_cl__accept_postpone; - b->quit = TRUE; - break; + /* 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; } - else if (opt->choice != svn_wc_conflict_choose_undefined) + + 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 (id == svn_client_conflict_option_incoming_move_file_text_merge || + id == svn_client_conflict_option_incoming_move_dir_merge) { - result->choice = opt->choice; - break; + SVN_ERR( + svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + possible_moved_to_repos_relpaths, builtin_option, + result_pool, iterpool)); + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + 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; } -/* The body of svn_cl__conflict_func_interactive(). */ +/* Make the user select a move target path for the moved-away VICTIM_ABSPATH. */ static svn_error_t * -conflict_func_interactive(svn_wc_conflict_result_t **result, - const svn_wc_conflict_description2_t *desc, - void *baton, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +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) { - svn_cl__interactive_conflict_baton_t *b = baton; - svn_error_t *err; + 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); - /* Start out assuming we're going to postpone the conflict. */ - *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, - NULL, result_pool); + 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; - switch (b->accept_which) + 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) { - case svn_cl__accept_invalid: - case svn_cl__accept_unspecified: - /* No (or no valid) --accept option, fall through to prompting. */ + 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_cmdline_fprintf(stderr, iterpool, "%s\n", + svn_err_best_message(err, buf, sizeof(buf))); + svn_error_clear(err); + continue; + } + break; - case svn_cl__accept_postpone: - (*result)->choice = svn_wc_conflict_choose_postpone; - return SVN_NO_ERROR; - case svn_cl__accept_base: - (*result)->choice = svn_wc_conflict_choose_base; - return SVN_NO_ERROR; - case svn_cl__accept_working: - /* If the caller didn't merge the property values, then I guess - * 'choose working' means 'choose mine'... */ - if (! desc->merged_file) - (*result)->merged_file = desc->my_abspath; - (*result)->choice = svn_wc_conflict_choose_merged; - return SVN_NO_ERROR; - case svn_cl__accept_mine_conflict: - (*result)->choice = svn_wc_conflict_choose_mine_conflict; - return SVN_NO_ERROR; - case svn_cl__accept_theirs_conflict: - (*result)->choice = svn_wc_conflict_choose_theirs_conflict; - return SVN_NO_ERROR; - case svn_cl__accept_mine_full: - (*result)->choice = svn_wc_conflict_choose_mine_full; - return SVN_NO_ERROR; - case svn_cl__accept_theirs_full: - (*result)->choice = svn_wc_conflict_choose_theirs_full; - return SVN_NO_ERROR; - case svn_cl__accept_edit: - if (desc->merged_file) + } + + svn_pool_destroy(iterpool); + + SVN_ERR_ASSERT((idx - 1) == (int)(idx - 1)); + *preferred_move_target_idx = (int)(idx - 1); + 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) { - if (b->external_failed) + int preferred_move_target_idx; + apr_array_header_t *options; + svn_client_conflict_option_t *conflict_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)); + conflict_option = + svn_client_conflict_option_find_by_id( + options, + svn_client_conflict_option_incoming_move_file_text_merge); + if (conflict_option == NULL) { - (*result)->choice = svn_wc_conflict_choose_postpone; - return SVN_NO_ERROR; + conflict_option = + svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_incoming_move_dir_merge); } - err = svn_cmdline__edit_file_externally(desc->merged_file, - b->editor_cmd, b->config, - scratch_pool); - if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR || - err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + if (conflict_option) { - 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); - b->external_failed = TRUE; + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath( + conflict_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); } - else if (err) - return svn_error_trace(err); - (*result)->choice = svn_wc_conflict_choose_merged; - return SVN_NO_ERROR; + continue; } - /* else, fall through to prompting. */ - break; - case svn_cl__accept_launch: - if (desc->base_abspath && desc->their_abspath - && desc->my_abspath && desc->merged_file) + else if (strcmp(opt->code, "w") == 0) { - svn_boolean_t remains_in_conflict; - - if (b->external_failed) + int preferred_move_target_idx; + apr_array_header_t *options; + svn_client_conflict_option_t *conflict_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)); + conflict_option = + svn_client_conflict_option_find_by_id( + options, + svn_client_conflict_option_incoming_move_file_text_merge); + if (conflict_option == NULL) { - (*result)->choice = svn_wc_conflict_choose_postpone; - return SVN_NO_ERROR; + conflict_option = + svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_incoming_move_dir_merge); } - err = svn_cl__merge_file_externally(desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, - b->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)) + if (conflict_option) { - 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)); - b->external_failed = TRUE; - return svn_error_trace(err); + SVN_ERR(svn_client_conflict_option_set_moved_to_abspath( + conflict_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)); } - else if (err) - return svn_error_trace(err); - - if (remains_in_conflict) - (*result)->choice = svn_wc_conflict_choose_postpone; - else - (*result)->choice = svn_wc_conflict_choose_merged; - return SVN_NO_ERROR; + continue; + } + else if (opt->choice != svn_client_conflict_option_undefined) + { + option_id = opt->choice; + break; } - /* else, fall through to prompting. */ - break; } - - /* Print a summary of conflicts before starting interactive resolution */ - if (! b->printed_summary) + svn_pool_destroy(iterpool); + if (option_id != svn_client_conflict_option_unspecified && + option_id != svn_client_conflict_option_postpone) { - SVN_ERR(svn_cl__print_conflict_stats(b->conflict_stats, scratch_pool)); - b->printed_summary = TRUE; + 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); } - /* We're in interactive mode and either the user gave no --accept - option or the option did not apply; let's prompt. */ - - /* Handle the most common cases, which is either: + return SVN_NO_ERROR; +} - Conflicting edits on a file's text, or - Conflicting edits on a property. - */ - if (((desc->kind == svn_wc_conflict_kind_text) - && (desc->action == svn_wc_conflict_action_edit) - && (desc->reason == svn_wc_conflict_reason_edited))) - SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool)); - else if (desc->kind == svn_wc_conflict_kind_property) - SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool)); - else if (desc->kind == svn_wc_conflict_kind_tree) - SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool)); +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 *result_pool, + 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)); - else /* other types of conflicts -- do nothing about them. */ + /* Print a summary of conflicts before starting interactive resolution */ + if (! *printed_summary) { - (*result)->choice = svn_wc_conflict_choose_postpone; + 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, result_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__conflict_func_interactive(svn_wc_conflict_result_t **result, - const svn_wc_conflict_description2_t *desc, - void *baton, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +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_cl__interactive_conflict_baton_t *b = baton; + 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; - SVN_ERR(conflict_func_interactive(result, desc, baton, - result_pool, scratch_pool)); + if (text_conflicted) + { + const char *mime_type = + svn_client_conflict_text_get_mime_type(conflict); - /* If we are resolving a conflict, adjust the summary of conflicts. */ - if ((*result)->choice != svn_wc_conflict_choose_postpone) + /* 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) { - const char *local_path - = svn_cl__local_style_skip_ancestor( - b->path_prefix, desc->local_abspath, scratch_pool); + 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; - svn_cl__conflict_stats_resolved(b->conflict_stats, local_path, - desc->kind); + 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; + + *quit = FALSE; + + while (!resolved && !postponed && !*quit) + { + err = resolve_conflict_interactively(&resolved, &postponed, quit, + external_failed, + printed_summary, + &printed_description, + conflict, + editor_cmd, ctx->config, + path_prefix, pb, + conflict_stats, ctx, + scratch_pool, scratch_pool); + 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); + } + } + 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; } |