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.c1380
1 files changed, 961 insertions, 419 deletions
diff --git a/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c
index a158576..0f12413 100644
--- a/subversion/svn/conflict-callbacks.c
+++ b/subversion/svn/conflict-callbacks.c
@@ -24,36 +24,66 @@
#include <apr_xlate.h> /* for APR_LOCALE_CHARSET */
-#define APR_WANT_STDIO
#define APR_WANT_STRFUNC
#include <apr_want.h>
+#include "svn_hash.h"
#include "svn_cmdline.h"
#include "svn_client.h"
+#include "svn_dirent_uri.h"
#include "svn_types.h"
#include "svn_pools.h"
+#include "svn_sorts.h"
+#include "svn_utf.h"
#include "cl.h"
+#include "cl-conflicts.h"
+
+#include "private/svn_cmdline_private.h"
#include "svn_private_config.h"
+#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0])))
+
+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_cl__conflict_baton_t *
-svn_cl__conflict_baton_make(svn_cl__accept_t accept_which,
- apr_hash_t *config,
- const char *editor_cmd,
- svn_cmdline_prompt_baton_t *pb,
- apr_pool_t *pool)
+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_cl__conflict_baton_t *b = apr_palloc(pool, sizeof(*b));
- b->accept_which = accept_which;
- b->config = config;
- b->editor_cmd = editor_cmd;
- b->external_failed = FALSE;
- b->pb = pb;
- return b;
+ 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;
+
+ return SVN_NO_ERROR;
}
svn_cl__accept_t
@@ -92,31 +122,63 @@ svn_cl__accept_from_word(const char *word)
}
-/* Print on stdout a diff between the 'base' and 'merged' files, if both of
- * those are available, else between 'their' and 'my' files, of DESC. */
+/* Print on stdout a diff that shows incoming conflicting changes
+ * corresponding to the conflict described in DESC. */
static svn_error_t *
-show_diff(const svn_wc_conflict_description_t *desc,
+show_diff(const svn_wc_conflict_description2_t *desc,
+ const char *path_prefix,
apr_pool_t *pool)
{
const char *path1, *path2;
+ const char *label1, *label2;
svn_diff_t *diff;
svn_stream_t *output;
svn_diff_file_options_t *options;
- if (desc->merged_file && desc->base_file)
+ if (desc->merged_file)
{
- /* Show the conflict markers to the user */
- path1 = desc->base_file;
+ /* For conflicts recorded by the 'merge' operation, show a diff between
+ * 'mine' (the working version of the file as it appeared before the
+ * 'merge' operation was run) and 'merged' (the version of the file
+ * as it appears after the merge operation).
+ *
+ * For conflicts recorded by the 'update' and 'switch' operations,
+ * show a diff beween '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)
+ {
+ path1 = desc->my_abspath;
+ label1 = _("MINE");
+ }
+ else
+ {
+ path1 = desc->their_abspath;
+ label1 = _("THEIRS");
+ }
path2 = desc->merged_file;
+ label2 = _("MERGED");
}
else
{
- /* There's no base file, but we can show the
+ /* There's no merged file, but we can show the
difference between mine and theirs. */
- path1 = desc->their_file;
- path2 = desc->my_file;
+ path1 = desc->their_abspath;
+ label1 = _("THEIRS");
+ path2 = desc->my_abspath;
+ label2 = _("MINE");
}
+ label1 = apr_psprintf(pool, "%s\t- %s",
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, path1, pool), label1);
+ label2 = apr_psprintf(pool, "%s\t- %s",
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, path2, pool), label2);
+
options = svn_diff_file_options_create(pool);
options->ignore_eol_style = TRUE;
SVN_ERR(svn_stream_for_stdout(&output, pool));
@@ -124,7 +186,7 @@ show_diff(const svn_wc_conflict_description_t *desc,
options, pool));
return svn_diff_file_output_unified3(output, diff,
path1, path2,
- NULL, NULL,
+ label1, label2,
APR_LOCALE_CHARSET,
NULL, FALSE,
pool);
@@ -134,7 +196,7 @@ show_diff(const svn_wc_conflict_description_t *desc,
/* Print on stdout just the conflict hunks of a diff among the 'base', 'their'
* and 'my' files of DESC. */
static svn_error_t *
-show_conflicts(const svn_wc_conflict_description_t *desc,
+show_conflicts(const svn_wc_conflict_description2_t *desc,
apr_pool_t *pool)
{
svn_diff_t *diff;
@@ -145,16 +207,16 @@ show_conflicts(const svn_wc_conflict_description_t *desc,
options->ignore_eol_style = TRUE;
SVN_ERR(svn_stream_for_stdout(&output, pool));
SVN_ERR(svn_diff_file_diff3_2(&diff,
- desc->base_file,
- desc->my_file,
- desc->their_file,
+ desc->base_abspath,
+ desc->my_abspath,
+ desc->their_abspath,
options, pool));
/* ### Consider putting the markers/labels from
### svn_wc__merge_internal in the conflict description. */
return svn_diff_file_output_merge2(output, diff,
- desc->base_file,
- desc->my_file,
- desc->their_file,
+ desc->base_abspath,
+ desc->my_abspath,
+ desc->their_abspath,
_("||||||| ORIGINAL"),
_("<<<<<<< MINE (select with 'mc')"),
_(">>>>>>> THEIRS (select with 'tc')"),
@@ -163,8 +225,85 @@ show_conflicts(const svn_wc_conflict_description_t *desc,
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.
+ *
+ * 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,
+ 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
+ * 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;
+ }
+
+ 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_merge2(output, diff,
+ base_abspath,
+ merged_abspath ? merged_abspath
+ : my_abspath,
+ their_abspath,
+ _("||||||| ORIGINAL"),
+ _("<<<<<<< MINE"),
+ _(">>>>>>> THEIRS"),
+ "=======",
+ svn_diff_conflict_display_modified_original_latest,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Display the conflicting values of a property as a 3-way diff.
+ *
+ * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of
+ * DESC->MY_ABSPATH.
+ *
+ * Assume the values are printable UTF-8 text.
+ */
+static svn_error_t *
+show_prop_conflict(const svn_wc_conflict_description2_t *desc,
+ const char *merged_abspath,
+ apr_pool_t *pool)
+{
+ svn_stream_t *output;
+
+ SVN_ERR(svn_stream_for_stdout(&output, pool));
+ SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, pool));
-/* Run an external editor, passing it the 'merged' file in DESC, or, if the
+ return SVN_NO_ERROR;
+}
+
+/* Run an external editor, passing it the MERGED_FILE, 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.
@@ -175,27 +314,31 @@ show_conflicts(const svn_wc_conflict_description_t *desc,
* return that error. */
static svn_error_t *
open_editor(svn_boolean_t *performed_edit,
- const svn_wc_conflict_description_t *desc,
- svn_cl__conflict_baton_t *b,
+ const char *merged_file,
+ svn_cl__interactive_conflict_baton_t *b,
apr_pool_t *pool)
{
svn_error_t *err;
- if (desc->merged_file)
+ if (merged_file)
{
- err = svn_cl__edit_file_externally(desc->merged_file, b->editor_cmd,
- b->config, pool);
+ err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd,
+ b->config, pool);
if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
{
+ svn_error_t *root_err = svn_error_root_cause(err);
+
SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
- err->message ? err->message :
+ root_err->message ? root_err->message :
_("No editor found.")));
svn_error_clear(err);
}
else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
{
+ svn_error_t *root_err = svn_error_root_cause(err);
+
SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
- err->message ? err->message :
+ root_err->message ? root_err->message :
_("Error running editor.")));
svn_error_clear(err);
}
@@ -212,6 +355,34 @@ open_editor(svn_boolean_t *performed_edit,
return SVN_NO_ERROR;
}
+/* Run an external editor, passing it the 'merged' property in DESC.
+ * 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,
+ 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, scratch_pool));
+ SVN_ERR(svn_stream_close(merged_prop));
+ SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
+ SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool));
+ *merged_file_path = (performed_edit ? file_path : NULL);
+
+ return SVN_NO_ERROR;
+}
/* Run an external merge tool, passing it the 'base', 'their', 'my' and
* 'merged' files in DESC. The tool to use is determined by B->config and
@@ -223,27 +394,30 @@ open_editor(svn_boolean_t *performed_edit,
* return that error. */
static svn_error_t *
launch_resolver(svn_boolean_t *performed_edit,
- const svn_wc_conflict_description_t *desc,
- svn_cl__conflict_baton_t *b,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
apr_pool_t *pool)
{
svn_error_t *err;
- err = svn_cl__merge_file_externally(desc->base_file, desc->their_file,
- desc->my_file, desc->merged_file,
- desc->path, b->config, NULL, pool);
+ err = svn_cl__merge_file_externally(desc->base_abspath, desc->their_abspath,
+ desc->my_abspath, desc->merged_file,
+ desc->local_abspath, b->config, NULL,
+ pool);
if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
{
SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
err->message ? err->message :
- _("No merge tool found.\n")));
+ _("No merge tool found, "
+ "try '(m) merge' instead.\n")));
svn_error_clear(err);
}
else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
{
SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
err->message ? err->message :
- _("Error running merge tool.")));
+ _("Error running merge tool, "
+ "try '(m) merge' instead.")));
svn_error_clear(err);
}
else if (err)
@@ -255,21 +429,705 @@ launch_resolver(svn_boolean_t *performed_edit,
}
-/* Implement svn_wc_conflict_resolver_func_t; resolves based on
- --accept option if given, else by prompting. */
-svn_error_t *
-svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
- const svn_wc_conflict_description_t *desc,
- void *baton,
- apr_pool_t *pool)
+/* Maximum line length for the prompt string. */
+#define MAX_PROMPT_WIDTH 70
+
+/* Description of a resolver option */
+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 -1 if not a simple choice */
+} 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[] =
+{
+ /* 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]"),
+ -1 },
+ { "df", N_("show diff"), N_("show all changes made to merged file"),
+ -1 },
+ { "r", N_("mark resolved"), N_("accept merged version of file"),
+ svn_wc_conflict_choose_merged },
+ { "", "", "", svn_wc_conflict_choose_unspecified },
+ { "dc", N_("display conflict"), N_("show all conflicts "
+ "(ignoring merged version)"), -1 },
+ { "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 internal merge tool to resolve "
+ "conflict"), -1 },
+ { "l", N_("launch tool"), N_("launch external tool to resolve "
+ "conflict [launch]"), -1 },
+ { "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', '?')"), -1 },
+ { 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"), -1 },
+ { "e", N_("edit property"), N_("change merged property value in an editor"
+ " [edit]"), -1 },
+ { "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 '?')"), -1 },
+ { NULL }
+};
+
+/* Resolver options for a tree conflict */
+static const resolver_option_t tree_conflict_options[] =
+{
+ { "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 '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_moved_away[] =
+{
+ { "mc", N_("apply update (recommended)"),
+ N_("apply update to the move destination"
+ " [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("discard update (breaks move)"), N_("discard update, mark "
+ "resolved, the move will "
+ "will become a copy"),
+ 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 '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_edit_moved_away[] =
+{
+ { "mc", N_("apply update to move destination"),
+ 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 '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_deleted[] =
+{
+ { "mc", N_("keep affected local moves"), N_("keep any local moves affected "
+ "by this deletion [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("mark resolved (breaks moves)"), N_("mark resolved, any affected "
+ "moves will become copies"),
+ 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 '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_replaced[] =
+{
+ { "mc", N_("keep affected local moves"), N_("keep any moves affected by this "
+ "replacement [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("mark resolved (breaks moves)"), N_("mark resolved (any affected "
+ "moves will become copies)"),
+ 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 '?')"), -1 },
+ { 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,
+ const char *option_code)
+{
+ const resolver_option_t *opt;
+
+ for (opt = options; opt->code; opt++)
+ {
+ /* Ignore code "" (blank lines) which is not a valid answer. */
+ if (opt->code[0] && strcmp(opt->code, option_code) == 0)
+ return opt;
+ }
+ return NULL;
+}
+
+/* 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,
+ const char *const *option_codes,
+ apr_pool_t *pool)
+{
+ const char *result = _("Select:");
+ int left_margin = svn_utf_cstring_utf8_width(result);
+ const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
+ int this_line_len = left_margin;
+ svn_boolean_t first = TRUE;
+
+ while (1)
+ {
+ const resolver_option_t *opt;
+ const char *s;
+ int slen;
+
+ if (option_codes)
+ {
+ if (! *option_codes)
+ break;
+ opt = find_option(options, *option_codes++);
+ }
+ else
+ {
+ opt = options++;
+ if (! opt->code)
+ break;
+ }
+
+ if (! first)
+ result = apr_pstrcat(pool, result, ",", (char *)NULL);
+ s = apr_psprintf(pool, _(" (%s) %s"),
+ opt->code, _(opt->short_desc));
+ slen = svn_utf_cstring_utf8_width(s);
+ /* Break the line if adding the next option would make it too long */
+ if (this_line_len + slen > MAX_PROMPT_WIDTH)
+ {
+ result = apr_pstrcat(pool, result, line_sep, (char *)NULL);
+ this_line_len = left_margin;
+ }
+ result = apr_pstrcat(pool, result, s, (char *)NULL);
+ this_line_len += slen;
+ first = FALSE;
+ }
+ return apr_pstrcat(pool, result, ": ", (char *)NULL);
+}
+
+/* Return a help string listing the OPTIONS. */
+static const char *
+help_string(const resolver_option_t *options,
+ apr_pool_t *pool)
+{
+ const char *result = "";
+ const resolver_option_t *opt;
+
+ for (opt = options; opt->code; opt++)
+ {
+ /* 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));
+ }
+ else
+ {
+ result = apr_pstrcat(pool, result, "\n", (char *)NULL);
+ }
+ }
+ result = apr_pstrcat(pool, result,
+ _("Words in square brackets are the corresponding "
+ "--accept option arguments.\n"),
+ (char *)NULL);
+ return result;
+}
+
+/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
+ * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen
+ * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
+ * NULL if the answer was not one of them.
+ *
+ * If the answer is the (globally recognized) 'help' option, then display
+ * the 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,
+ const char *const *options_to_show,
+ void *prompt_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *prompt
+ = prompt_string(conflict_options, options_to_show, scratch_pool);
+ const char *answer;
+
+ SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
+ if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
+ help_string(conflict_options,
+ scratch_pool)));
+ *opt = NULL;
+ }
+ else
+ {
+ *opt = find_option(conflict_options, answer);
+ if (! *opt)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Unrecognized option.\n\n")));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* 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.
+ * 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,
+ 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)? */
+ 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;
+
+ SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Conflict discovered in file '%s'.\n"),
+ svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath,
+ scratch_pool)));
+
+ /* Diffing can happen between base and merged, to show conflict
+ markers to the user (this is the typical 3-way merge
+ scenario), or if no base is available, we can show a diff
+ between mine and theirs. */
+ if ((desc->merged_file && desc->base_abspath)
+ || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
+ diff_allowed = TRUE;
+
+ while (TRUE)
+ {
+ const char *options[ARRAY_LEN(text_conflict_options)];
+ const char **next_option = options;
+ const resolver_option_t *opt;
+
+ svn_pool_clear(iterpool);
+
+ *next_option++ = "p";
+ if (diff_allowed)
+ {
+ *next_option++ = "df";
+ *next_option++ = "e";
+ *next_option++ = "m";
+
+ if (knows_something)
+ *next_option++ = "r";
+
+ if (! desc->is_binary)
+ {
+ *next_option++ = "mc";
+ *next_option++ = "tc";
+ }
+ }
+ else
+ {
+ if (knows_something)
+ *next_option++ = "r";
+ *next_option++ = "mf";
+ *next_option++ = "tf";
+ }
+ *next_option++ = "s";
+ *next_option++ = NULL;
+
+ SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->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;
+ break;
+ }
+ else if (strcmp(opt->code, "s") == 0)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
+ help_string(text_conflict_options,
+ iterpool)));
+ }
+ else if (strcmp(opt->code, "dc") == 0)
+ {
+ if (desc->is_binary)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; cannot "
+ "display conflicts for a "
+ "binary file.\n\n")));
+ continue;
+ }
+ else if (! (desc->my_abspath && desc->base_abspath &&
+ desc->their_abspath))
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; original "
+ "files not available.\n\n")));
+ continue;
+ }
+ SVN_ERR(show_conflicts(desc, iterpool));
+ knows_something = TRUE;
+ }
+ else if (strcmp(opt->code, "df") == 0)
+ {
+ if (! diff_allowed)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; there's no "
+ "merged version to diff.\n\n")));
+ continue;
+ }
+
+ SVN_ERR(show_diff(desc, b->path_prefix, 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));
+ if (performed_edit)
+ knows_something = TRUE;
+ }
+ else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
+ strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
+ {
+ if (desc->kind != svn_wc_conflict_kind_text)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; can only "
+ "resolve text conflicts with "
+ "the internal merge tool."
+ "\n\n")));
+ continue;
+ }
+
+ if (desc->base_abspath && desc->their_abspath &&
+ desc->my_abspath && desc->merged_file)
+ {
+ svn_boolean_t remains_in_conflict;
+
+ SVN_ERR(svn_cl__merge_file(desc->base_abspath,
+ desc->their_abspath,
+ desc->my_abspath,
+ desc->merged_file,
+ desc->local_abspath,
+ b->path_prefix,
+ b->editor_cmd,
+ b->config,
+ &remains_in_conflict,
+ iterpool));
+ knows_something = !remains_in_conflict;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option.\n\n")));
+ }
+ else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
+ {
+ /* ### This check should be earlier as it's nasty to offer an option
+ * and then when the user chooses it say 'Invalid option'. */
+ /* ### 'merged_file' 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)
+ {
+ SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool));
+ if (performed_edit)
+ knows_something = TRUE;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option.\n\n")));
+ }
+ else if (opt->choice != -1)
+ {
+ if ((opt->choice == svn_wc_conflict_choose_mine_conflict
+ || opt->choice == svn_wc_conflict_choose_theirs_conflict)
+ && desc->is_binary)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; cannot choose "
+ "based on conflicts in a "
+ "binary file.\n\n")));
+ continue;
+ }
+
+ /* We only allow the user accept the merged version of
+ the file if they've edited it, or at least looked at
+ the diff. */
+ if (opt->choice == svn_wc_conflict_choose_merged
+ && ! knows_something)
+ {
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, iterpool,
+ _("Invalid option; use diff/edit/merge/launch "
+ "before choosing 'mark resolved'.\n\n")));
+ continue;
+ }
+
+ result->choice = opt->choice;
+ if (performed_edit)
+ result->save_merged = TRUE;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ 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.
+ * 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)
+{
+ apr_pool_t *iterpool;
+ const char *message;
+ const char *merged_file_path = NULL;
+ svn_boolean_t resolved_allowed = FALSE;
+
+ /* ### 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_cmdline_fprintf(stderr, scratch_pool,
+ _("Conflict for property '%s' discovered"
+ " on '%s'.\n"),
+ desc->property_name,
+ svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath,
+ 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));
+
+ 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;
+
+ *next_option++ = "p";
+ *next_option++ = "mf";
+ *next_option++ = "tf";
+ *next_option++ = "dc";
+ *next_option++ = "e";
+ if (resolved_allowed)
+ *next_option++ = "r";
+ *next_option++ = "q";
+ *next_option++ = "h";
+ *next_option++ = NULL;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->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;
+ break;
+ }
+ else if (strcmp(opt->code, "dc") == 0)
+ {
+ SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool));
+ }
+ else if (strcmp(opt->code, "e") == 0)
+ {
+ SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b,
+ result_pool, scratch_pool));
+ resolved_allowed = (merged_file_path != NULL);
+ }
+ else if (strcmp(opt->code, "r") == 0)
+ {
+ if (! resolved_allowed)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; please edit the property "
+ "first.\n\n")));
+ continue;
+ }
+
+ result->merged_file = merged_file_path;
+ result->choice = svn_wc_conflict_choose_merged;
+ break;
+ }
+ else if (opt->choice != -1)
+ {
+ result->choice = opt->choice;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ 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.
+ * 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)
+{
+ const char *readable_desc;
+ apr_pool_t *iterpool;
+
+ 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));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (1)
+ {
+ const resolver_option_t *opt;
+ const resolver_option_t *tc_opts;
+
+ svn_pool_clear(iterpool);
+
+ if (desc->operation == svn_wc_operation_update ||
+ desc->operation == svn_wc_operation_switch)
+ {
+ if (desc->reason == svn_wc_conflict_reason_moved_away)
+ {
+ if (desc->action == svn_wc_conflict_action_edit)
+ tc_opts = tree_conflict_options_update_edit_moved_away;
+ else
+ tc_opts = tree_conflict_options_update_moved_away;
+ }
+ else if (desc->reason == svn_wc_conflict_reason_deleted)
+ tc_opts = tree_conflict_options_update_deleted;
+ else if (desc->reason == svn_wc_conflict_reason_replaced)
+ tc_opts = tree_conflict_options_update_replaced;
+ else
+ tc_opts = tree_conflict_options;
+ }
+ else
+ tc_opts = tree_conflict_options;
+
+ SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->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;
+ break;
+ }
+ else if (opt->choice != -1)
+ {
+ result->choice = opt->choice;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_cl__conflict_func_interactive(). */
+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)
{
- svn_cl__conflict_baton_t *b = baton;
+ svn_cl__interactive_conflict_baton_t *b = baton;
svn_error_t *err;
- apr_pool_t *subpool;
/* Start out assuming we're going to postpone the conflict. */
*result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
- NULL, pool);
+ NULL, result_pool);
switch (b->accept_which)
{
@@ -284,6 +1142,10 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
(*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:
@@ -307,11 +1169,12 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
return SVN_NO_ERROR;
}
- err = svn_cl__edit_file_externally(desc->merged_file,
- b->editor_cmd, b->config, pool);
+ 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))
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("No editor found;"
" leaving all conflicts.")));
@@ -320,7 +1183,7 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
}
else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("Error running editor;"
" leaving all conflicts.")));
@@ -335,8 +1198,8 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
/* else, fall through to prompting. */
break;
case svn_cl__accept_launch:
- if (desc->base_file && desc->their_file
- && desc->my_file && desc->merged_file)
+ if (desc->base_abspath && desc->their_abspath
+ && desc->my_abspath && desc->merged_file)
{
svn_boolean_t remains_in_conflict;
@@ -346,17 +1209,17 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
return SVN_NO_ERROR;
}
- err = svn_cl__merge_file_externally(desc->base_file,
- desc->their_file,
- desc->my_file,
+ err = svn_cl__merge_file_externally(desc->base_abspath,
+ desc->their_abspath,
+ desc->my_abspath,
desc->merged_file,
- desc->path,
+ desc->local_abspath,
b->config,
&remains_in_conflict,
- pool);
+ scratch_pool);
if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("No merge tool found;"
" leaving all conflicts.")));
@@ -365,7 +1228,7 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
}
else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("Error running merge tool;"
" leaving all conflicts.")));
@@ -387,371 +1250,50 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
/* We're in interactive mode and either the user gave no --accept
option or the option did not apply; let's prompt. */
- subpool = svn_pool_create(pool);
/* Handle the most common cases, which is either:
Conflicting edits on a file's text, or
Conflicting edits on a property.
*/
- if (((desc->node_kind == svn_node_file)
+ if (((desc->kind == svn_wc_conflict_kind_text)
&& (desc->action == svn_wc_conflict_action_edit)
- && (desc->reason == svn_wc_conflict_reason_edited))
- || (desc->kind == svn_wc_conflict_kind_property))
- {
- const char *answer;
- char *prompt;
- 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)? */
- 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;
-
- if (desc->kind == svn_wc_conflict_kind_text)
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Conflict discovered in '%s'.\n"),
- desc->path));
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Conflict for property '%s' discovered"
- " on '%s'.\n"),
- desc->property_name, desc->path));
-
- if ((!desc->my_file && desc->their_file)
- || (desc->my_file && !desc->their_file))
- {
- /* One agent wants to change the property, one wants to
- delete it. This is not something we can diff, so we
- just tell the user. */
- svn_stringbuf_t *myval = NULL, *theirval = NULL;
-
- if (desc->my_file)
- {
- SVN_ERR(svn_stringbuf_from_file2(&myval, desc->my_file,
- subpool));
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("They want to delete the property, "
- "you want to change the value to '%s'.\n"),
- myval->data));
- }
- else
- {
- SVN_ERR(svn_stringbuf_from_file2(&theirval, desc->their_file,
- subpool));
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("They want to change the property value to '%s', "
- "you want to delete the property.\n"),
- theirval->data));
- }
- }
- }
- else
- /* We don't recognize any other sort of conflict yet */
- return SVN_NO_ERROR;
-
- /* Diffing can happen between base and merged, to show conflict
- markers to the user (this is the typical 3-way merge
- scenario), or if no base is available, we can show a diff
- between mine and theirs. */
- if ((desc->merged_file && desc->base_file)
- || (!desc->base_file && desc->my_file && desc->their_file))
- diff_allowed = TRUE;
-
- while (TRUE)
- {
- svn_pool_clear(subpool);
-
- prompt = apr_pstrdup(subpool, _("Select: (p) postpone"));
-
- if (diff_allowed)
- {
- prompt = apr_pstrcat(subpool, prompt,
- _(", (df) diff-full, (e) edit"),
- (char *)NULL);
-
- if (knows_something)
- prompt = apr_pstrcat(subpool, prompt, _(", (r) resolved"),
- (char *)NULL);
-
- if (! desc->is_binary &&
- desc->kind != svn_wc_conflict_kind_property)
- prompt = apr_pstrcat(subpool, prompt,
- _(",\n (mc) mine-conflict, "
- "(tc) theirs-conflict"),
- (char *)NULL);
- }
- else
- {
- if (knows_something)
- prompt = apr_pstrcat(subpool, prompt, _(", (r) resolved"),
- (char *)NULL);
- prompt = apr_pstrcat(subpool, prompt,
- _(",\n "
- "(mf) mine-full, (tf) theirs-full"),
- (char *)NULL);
- }
-
- prompt = apr_pstrcat(subpool, prompt, ",\n ", (char *)NULL);
- prompt = apr_pstrcat(subpool, prompt,
- _("(s) show all options: "),
- (char *)NULL);
+ && (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));
- SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool));
-
- if (strcmp(answer, "s") == 0)
- {
- /* These are used in svn_cl__accept_from_word(). */
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("\n"
- " (e) edit - change merged file in an editor\n"
- " (df) diff-full - show all changes made to merged "
- "file\n"
- " (r) resolved - accept merged version of file\n"
- "\n"
- " (dc) display-conflict - show all conflicts "
- "(ignoring merged version)\n"
- " (mc) mine-conflict - accept my version for all "
- "conflicts (same)\n"
- " (tc) theirs-conflict - accept their version for all "
- "conflicts (same)\n"
- "\n"
- " (mf) mine-full - accept my version of entire file "
- "(even non-conflicts)\n"
- " (tf) theirs-full - accept their version of entire "
- "file (same)\n"
- "\n"
- " (p) postpone - mark the conflict to be "
- "resolved later\n"
- " (l) launch - launch external tool to "
- "resolve conflict\n"
- " (s) show all - show this list\n\n")));
- }
- else if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0)
- {
- /* Do nothing, let file be marked conflicted. */
- (*result)->choice = svn_wc_conflict_choose_postpone;
- break;
- }
- else if (strcmp(answer, "mc") == 0 || strcmp(answer, "X-)") == 0)
- {
- if (desc->is_binary)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts in a "
- "binary file.\n\n")));
- continue;
- }
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts for "
- "properties.\n\n")));
- continue;
- }
-
- (*result)->choice = svn_wc_conflict_choose_mine_conflict;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "tc") == 0 || strcmp(answer, "X-(") == 0)
- {
- if (desc->is_binary)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts in a "
- "binary file.\n\n")));
- continue;
- }
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts for "
- "properties.\n\n")));
- continue;
- }
- (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "mf") == 0 || strcmp(answer, ":-)") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_mine_full;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "tf") == 0 || strcmp(answer, ":-(") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_theirs_full;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "dc") == 0)
- {
- if (desc->is_binary)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot "
- "display conflicts for a "
- "binary file.\n\n")));
- continue;
- }
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot "
- "display conflicts for "
- "properties.\n\n")));
- continue;
- }
- else if (! (desc->my_file && desc->base_file && desc->their_file))
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; original "
- "files not available.\n\n")));
- continue;
- }
- SVN_ERR(show_conflicts(desc, subpool));
- knows_something = TRUE;
- }
- else if (strcmp(answer, "df") == 0)
- {
- if (! diff_allowed)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; there's no "
- "merged version to diff.\n\n")));
- continue;
- }
-
- SVN_ERR(show_diff(desc, subpool));
- knows_something = TRUE;
- }
- else if (strcmp(answer, "e") == 0 || strcmp(answer, ":-E") == 0)
- {
- SVN_ERR(open_editor(&performed_edit, desc, b, subpool));
- if (performed_edit)
- knows_something = TRUE;
- }
- else if (strcmp(answer, "l") == 0 || strcmp(answer, ":-l") == 0)
- {
- if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot "
- "resolve property conflicts "
- "with an external merge tool."
- "\n\n")));
- continue;
- }
- if (desc->base_file && desc->their_file && desc->my_file
- && desc->merged_file)
- {
- SVN_ERR(launch_resolver(&performed_edit, desc, b, subpool));
- if (performed_edit)
- knows_something = TRUE;
- }
- else
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option.\n\n")));
- }
- else if (strcmp(answer, "r") == 0)
- {
- /* 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 (knows_something)
- {
- (*result)->choice = svn_wc_conflict_choose_merged;
- break;
- }
- else
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option.\n\n")));
- }
- }
+ else /* other types of conflicts -- do nothing about them. */
+ {
+ (*result)->choice = svn_wc_conflict_choose_postpone;
}
- /*
- Dealing with obstruction of additions can be tricky. The
- obstructing item could be unversioned, versioned, or even
- schedule-add. Here's a matrix of how the caller should behave,
- based on results we return.
-
- Unversioned Versioned Schedule-Add
- choose_mine skip addition, skip addition skip addition
- add existing item
+ return SVN_NO_ERROR;
+}
- choose_theirs destroy file, schedule-delete, revert add,
- add new item. add new item. rm file,
- add new item
+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__interactive_conflict_baton_t *b = baton;
- postpone [ bail out ]
+ SVN_ERR(conflict_func_interactive(result, desc, baton,
+ result_pool, scratch_pool));
- */
- else if ((desc->action == svn_wc_conflict_action_add)
- && (desc->reason == svn_wc_conflict_reason_obstructed))
+ /* If we are resolving a conflict, adjust the summary of conflicts. */
+ if ((*result)->choice != svn_wc_conflict_choose_postpone)
{
- const char *answer;
- const char *prompt;
-
- SVN_ERR(svn_cmdline_fprintf(
- stderr, subpool,
- _("Conflict discovered when trying to add '%s'.\n"
- "An object of the same name already exists.\n"),
- desc->path));
- prompt = _("Select: (p) postpone, (mf) mine-full, "
- "(tf) theirs-full, (h) help:");
-
- while (1)
- {
- svn_pool_clear(subpool);
-
- SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool));
+ const char *local_path
+ = svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath, scratch_pool);
- if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _(" (p) postpone - resolve the conflict later\n"
- " (mf) mine-full - accept pre-existing item "
- "(ignore upstream addition)\n"
- " (tf) theirs-full - accept incoming item "
- "(overwrite pre-existing item)\n"
- " (h) help - show this help\n\n")));
- }
- if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_postpone;
- break;
- }
- if (strcmp(answer, "mf") == 0 || strcmp(answer, ":-)") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_mine_full;
- break;
- }
- if (strcmp(answer, "tf") == 0 || strcmp(answer, ":-(") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_theirs_full;
- break;
- }
- }
+ svn_cl__conflict_stats_resolved(b->conflict_stats, local_path,
+ desc->kind);
}
-
- else /* other types of conflicts -- do nothing about them. */
- {
- (*result)->choice = svn_wc_conflict_choose_postpone;
- }
-
- svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}