summaryrefslogtreecommitdiff
path: root/subversion/svn/conflict-callbacks.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svn/conflict-callbacks.c')
-rw-r--r--subversion/svn/conflict-callbacks.c2103
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;
}