diff options
| author | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 | 
|---|---|---|
| committer | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 | 
| commit | 3faf8d6bffc5d0fb2525ba37bb504c53366caf9d (patch) | |
| tree | 7e47911263e75034b767fe34b2f8d3d17e91f66d /subversion/svn/conflict-callbacks.c | |
| parent | a55fb3c0d5eca7d887798125d5b95942b1f01d4b (diff) | |
Diffstat (limited to 'subversion/svn/conflict-callbacks.c')
| -rw-r--r-- | subversion/svn/conflict-callbacks.c | 2103 | 
1 files changed, 1486 insertions, 617 deletions
| diff --git a/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c index a9cb39a2ade1..278fffcdb882 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;  } | 
