conflict-callbacks.c revision 289166
1226633Sdim/*
2193326Sed * conflict-callbacks.c: conflict resolution callbacks specific to the
3193326Sed * commandline client.
4193326Sed *
5193326Sed * ====================================================================
6193326Sed *    Licensed to the Apache Software Foundation (ASF) under one
7193326Sed *    or more contributor license agreements.  See the NOTICE file
8193326Sed *    distributed with this work for additional information
9193326Sed *    regarding copyright ownership.  The ASF licenses this file
10193326Sed *    to you under the Apache License, Version 2.0 (the
11193326Sed *    "License"); you may not use this file except in compliance
12193326Sed *    with the License.  You may obtain a copy of the License at
13193326Sed *
14193326Sed *      http://www.apache.org/licenses/LICENSE-2.0
15193326Sed *
16193326Sed *    Unless required by applicable law or agreed to in writing,
17249423Sdim *    software distributed under the License is distributed on an
18226633Sdim *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19226633Sdim *    KIND, either express or implied.  See the License for the
20226633Sdim *    specific language governing permissions and limitations
21193326Sed *    under the License.
22193326Sed * ====================================================================
23193326Sed */
24226633Sdim
25226633Sdim#include <apr_xlate.h>  /* for APR_LOCALE_CHARSET */
26193326Sed
27193326Sed#define APR_WANT_STRFUNC
28193326Sed#include <apr_want.h>
29226633Sdim
30226633Sdim#include "svn_hash.h"
31226633Sdim#include "svn_cmdline.h"
32226633Sdim#include "svn_client.h"
33226633Sdim#include "svn_dirent_uri.h"
34198092Srdivacky#include "svn_types.h"
35226633Sdim#include "svn_pools.h"
36226633Sdim#include "svn_sorts.h"
37226633Sdim#include "svn_utf.h"
38226633Sdim
39193326Sed#include "cl.h"
40226633Sdim#include "cl-conflicts.h"
41226633Sdim
42193326Sed#include "private/svn_cmdline_private.h"
43226633Sdim
44226633Sdim#include "svn_private_config.h"
45226633Sdim
46193326Sed#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0])))
47226633Sdim
48226633Sdim
49226633Sdim
50226633Sdimstruct svn_cl__interactive_conflict_baton_t {
51226633Sdim  svn_cl__accept_t accept_which;
52226633Sdim  apr_hash_t *config;
53193326Sed  const char *editor_cmd;
54226633Sdim  svn_boolean_t external_failed;
55234353Sdim  svn_cmdline_prompt_baton_t *pb;
56234353Sdim  const char *path_prefix;
57234353Sdim  svn_boolean_t quit;
58226633Sdim  svn_cl__conflict_stats_t *conflict_stats;
59226633Sdim};
60226633Sdim
61193326Sedsvn_error_t *
62226633Sdimsvn_cl__get_conflict_func_interactive_baton(
63226633Sdim  svn_cl__interactive_conflict_baton_t **b,
64226633Sdim  svn_cl__accept_t accept_which,
65226633Sdim  apr_hash_t *config,
66226633Sdim  const char *editor_cmd,
67226633Sdim  svn_cl__conflict_stats_t *conflict_stats,
68226633Sdim  svn_cancel_func_t cancel_func,
69226633Sdim  void *cancel_baton,
70198092Srdivacky  apr_pool_t *result_pool)
71193326Sed{
72226633Sdim  svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb));
73226633Sdim  pb->cancel_func = cancel_func;
74226633Sdim  pb->cancel_baton = cancel_baton;
75234353Sdim
76226633Sdim  *b = apr_palloc(result_pool, sizeof(**b));
77226633Sdim  (*b)->accept_which = accept_which;
78226633Sdim  (*b)->config = config;
79193326Sed  (*b)->editor_cmd = editor_cmd;
80226633Sdim  (*b)->external_failed = FALSE;
81226633Sdim  (*b)->pb = pb;
82226633Sdim  SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool));
83193326Sed  (*b)->quit = FALSE;
84193326Sed  (*b)->conflict_stats = conflict_stats;
85193326Sed
86226633Sdim  return SVN_NO_ERROR;
87226633Sdim}
88226633Sdim
89193326Sedsvn_cl__accept_t
90226633Sdimsvn_cl__accept_from_word(const char *word)
91226633Sdim{
92226633Sdim  /* Shorthand options are consistent with  svn_cl__conflict_handler(). */
93226633Sdim  if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0
94226633Sdim      || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0)
95198092Srdivacky    return svn_cl__accept_postpone;
96226633Sdim  if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0)
97226633Sdim    /* ### shorthand? */
98234353Sdim    return svn_cl__accept_base;
99226633Sdim  if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0)
100226633Sdim    /* ### shorthand? */
101226633Sdim    return svn_cl__accept_working;
102226633Sdim  if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0
103226633Sdim      || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0)
104226633Sdim    return svn_cl__accept_mine_conflict;
105226633Sdim  if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0
106226633Sdim      || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0)
107193326Sed    return svn_cl__accept_theirs_conflict;
108226633Sdim  if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0
109226633Sdim      || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0)
110226633Sdim    return svn_cl__accept_mine_full;
111234353Sdim  if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0
112226633Sdim      || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0)
113226633Sdim    return svn_cl__accept_theirs_full;
114226633Sdim  if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0
115226633Sdim      || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0)
116226633Sdim    return svn_cl__accept_edit;
117226633Sdim  if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0
118193326Sed      || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0)
119193326Sed    return svn_cl__accept_launch;
120193326Sed  /* word is an invalid action. */
121  return svn_cl__accept_invalid;
122}
123
124
125/* Print on stdout a diff that shows incoming conflicting changes
126 * corresponding to the conflict described in DESC. */
127static svn_error_t *
128show_diff(const svn_wc_conflict_description2_t *desc,
129          const char *path_prefix,
130          apr_pool_t *pool)
131{
132  const char *path1, *path2;
133  const char *label1, *label2;
134  svn_diff_t *diff;
135  svn_stream_t *output;
136  svn_diff_file_options_t *options;
137
138  if (desc->merged_file)
139    {
140      /* For conflicts recorded by the 'merge' operation, show a diff between
141       * 'mine' (the working version of the file as it appeared before the
142       * 'merge' operation was run) and 'merged' (the version of the file
143       * as it appears after the merge operation).
144       *
145       * For conflicts recorded by the 'update' and 'switch' operations,
146       * show a diff beween 'theirs' (the new pristine version of the
147       * file) and 'merged' (the version of the file as it appears with
148       * local changes merged with the new pristine version).
149       *
150       * This way, the diff is always minimal and clearly identifies changes
151       * brought into the working copy by the update/switch/merge operation. */
152      if (desc->operation == svn_wc_operation_merge)
153        {
154          path1 = desc->my_abspath;
155          label1 = _("MINE");
156        }
157      else
158        {
159          path1 = desc->their_abspath;
160          label1 = _("THEIRS");
161        }
162      path2 = desc->merged_file;
163      label2 = _("MERGED");
164    }
165  else
166    {
167      /* There's no merged file, but we can show the
168         difference between mine and theirs. */
169      path1 = desc->their_abspath;
170      label1 = _("THEIRS");
171      path2 = desc->my_abspath;
172      label2 = _("MINE");
173    }
174
175  label1 = apr_psprintf(pool, "%s\t- %s",
176                        svn_cl__local_style_skip_ancestor(
177                          path_prefix, path1, pool), label1);
178  label2 = apr_psprintf(pool, "%s\t- %s",
179                        svn_cl__local_style_skip_ancestor(
180                          path_prefix, path2, pool), label2);
181
182  options = svn_diff_file_options_create(pool);
183  options->ignore_eol_style = TRUE;
184  SVN_ERR(svn_stream_for_stdout(&output, pool));
185  SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2,
186                               options, pool));
187  return svn_diff_file_output_unified3(output, diff,
188                                       path1, path2,
189                                       label1, label2,
190                                       APR_LOCALE_CHARSET,
191                                       NULL, FALSE,
192                                       pool);
193}
194
195
196/* Print on stdout just the conflict hunks of a diff among the 'base', 'their'
197 * and 'my' files of DESC. */
198static svn_error_t *
199show_conflicts(const svn_wc_conflict_description2_t *desc,
200               apr_pool_t *pool)
201{
202  svn_diff_t *diff;
203  svn_stream_t *output;
204  svn_diff_file_options_t *options;
205
206  options = svn_diff_file_options_create(pool);
207  options->ignore_eol_style = TRUE;
208  SVN_ERR(svn_stream_for_stdout(&output, pool));
209  SVN_ERR(svn_diff_file_diff3_2(&diff,
210                                desc->base_abspath,
211                                desc->my_abspath,
212                                desc->their_abspath,
213                                options, pool));
214  /* ### Consider putting the markers/labels from
215     ### svn_wc__merge_internal in the conflict description. */
216  return svn_diff_file_output_merge2(output, diff,
217                                     desc->base_abspath,
218                                     desc->my_abspath,
219                                     desc->their_abspath,
220                                     _("||||||| ORIGINAL"),
221                                     _("<<<<<<< MINE (select with 'mc')"),
222                                     _(">>>>>>> THEIRS (select with 'tc')"),
223                                     "=======",
224                                     svn_diff_conflict_display_only_conflicts,
225                                     pool);
226}
227
228/* Perform a 3-way merge of the conflicting values of a property,
229 * and write the result to the OUTPUT stream.
230 *
231 * If MERGED_ABSPATH is non-NULL, use it as 'my' version instead of
232 * DESC->MY_ABSPATH.
233 *
234 * Assume the values are printable UTF-8 text.
235 */
236static svn_error_t *
237merge_prop_conflict(svn_stream_t *output,
238                    const svn_wc_conflict_description2_t *desc,
239                    const char *merged_abspath,
240                    apr_pool_t *pool)
241{
242  const char *base_abspath = desc->base_abspath;
243  const char *my_abspath = desc->my_abspath;
244  const char *their_abspath = desc->their_abspath;
245  svn_diff_file_options_t *options = svn_diff_file_options_create(pool);
246  svn_diff_t *diff;
247
248  /* If any of the property values is missing, use an empty file instead
249   * for the purpose of showing a diff. */
250  if (! base_abspath || ! my_abspath || ! their_abspath)
251    {
252      const char *empty_file;
253
254      SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file,
255                                       NULL, svn_io_file_del_on_pool_cleanup,
256                                       pool, pool));
257      if (! base_abspath)
258        base_abspath = empty_file;
259      if (! my_abspath)
260        my_abspath = empty_file;
261      if (! their_abspath)
262        their_abspath = empty_file;
263    }
264
265  options->ignore_eol_style = TRUE;
266  SVN_ERR(svn_diff_file_diff3_2(&diff,
267                                base_abspath,
268                                merged_abspath ? merged_abspath : my_abspath,
269                                their_abspath,
270                                options, pool));
271  SVN_ERR(svn_diff_file_output_merge2(output, diff,
272                                      base_abspath,
273                                      merged_abspath ? merged_abspath
274                                                     : my_abspath,
275                                      their_abspath,
276                                      _("||||||| ORIGINAL"),
277                                      _("<<<<<<< MINE"),
278                                      _(">>>>>>> THEIRS"),
279                                      "=======",
280                                      svn_diff_conflict_display_modified_original_latest,
281                                      pool));
282
283  return SVN_NO_ERROR;
284}
285
286/* Display the conflicting values of a property as a 3-way diff.
287 *
288 * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of
289 * DESC->MY_ABSPATH.
290 *
291 * Assume the values are printable UTF-8 text.
292 */
293static svn_error_t *
294show_prop_conflict(const svn_wc_conflict_description2_t *desc,
295                   const char *merged_abspath,
296                   apr_pool_t *pool)
297{
298  svn_stream_t *output;
299
300  SVN_ERR(svn_stream_for_stdout(&output, pool));
301  SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, pool));
302
303  return SVN_NO_ERROR;
304}
305
306/* Run an external editor, passing it the MERGED_FILE, or, if the
307 * 'merged' file is null, return an error. The tool to use is determined by
308 * B->editor_cmd, B->config and environment variables; see
309 * svn_cl__edit_file_externally() for details.
310 *
311 * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
312 * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
313 * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
314 * return that error. */
315static svn_error_t *
316open_editor(svn_boolean_t *performed_edit,
317            const char *merged_file,
318            svn_cl__interactive_conflict_baton_t *b,
319            apr_pool_t *pool)
320{
321  svn_error_t *err;
322
323  if (merged_file)
324    {
325      err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd,
326                                              b->config, pool);
327      if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
328        {
329          svn_error_t *root_err = svn_error_root_cause(err);
330
331          SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
332                                      root_err->message ? root_err->message :
333                                      _("No editor found.")));
334          svn_error_clear(err);
335        }
336      else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
337        {
338          svn_error_t *root_err = svn_error_root_cause(err);
339
340          SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
341                                      root_err->message ? root_err->message :
342                                      _("Error running editor.")));
343          svn_error_clear(err);
344        }
345      else if (err)
346        return svn_error_trace(err);
347      else
348        *performed_edit = TRUE;
349    }
350  else
351    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
352                                _("Invalid option; there's no "
353                                  "merged version to edit.\n\n")));
354
355  return SVN_NO_ERROR;
356}
357
358/* Run an external editor, passing it the 'merged' property in DESC.
359 * The tool to use is determined by B->editor_cmd, B->config and
360 * environment variables; see svn_cl__edit_file_externally() for details. */
361static svn_error_t *
362edit_prop_conflict(const char **merged_file_path,
363                   const svn_wc_conflict_description2_t *desc,
364                   svn_cl__interactive_conflict_baton_t *b,
365                   apr_pool_t *result_pool,
366                   apr_pool_t *scratch_pool)
367{
368  apr_file_t *file;
369  const char *file_path;
370  svn_boolean_t performed_edit = FALSE;
371  svn_stream_t *merged_prop;
372
373  SVN_ERR(svn_io_open_unique_file3(&file, &file_path, NULL,
374                                   svn_io_file_del_on_pool_cleanup,
375                                   result_pool, scratch_pool));
376  merged_prop = svn_stream_from_aprfile2(file, TRUE /* disown */,
377                                         scratch_pool);
378  SVN_ERR(merge_prop_conflict(merged_prop, desc, NULL, scratch_pool));
379  SVN_ERR(svn_stream_close(merged_prop));
380  SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
381  SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool));
382  *merged_file_path = (performed_edit ? file_path : NULL);
383
384  return SVN_NO_ERROR;
385}
386
387/* Run an external merge tool, passing it the 'base', 'their', 'my' and
388 * 'merged' files in DESC. The tool to use is determined by B->config and
389 * environment variables; see svn_cl__merge_file_externally() for details.
390 *
391 * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
392 * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
393 * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
394 * return that error.  */
395static svn_error_t *
396launch_resolver(svn_boolean_t *performed_edit,
397                const svn_wc_conflict_description2_t *desc,
398                svn_cl__interactive_conflict_baton_t *b,
399                apr_pool_t *pool)
400{
401  svn_error_t *err;
402
403  err = svn_cl__merge_file_externally(desc->base_abspath, desc->their_abspath,
404                                      desc->my_abspath, desc->merged_file,
405                                      desc->local_abspath, b->config, NULL,
406                                      pool);
407  if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
408    {
409      SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
410                                  err->message ? err->message :
411                                  _("No merge tool found, "
412                                    "try '(m) merge' instead.\n")));
413      svn_error_clear(err);
414    }
415  else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
416    {
417      SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
418                                  err->message ? err->message :
419                             _("Error running merge tool, "
420                               "try '(m) merge' instead.")));
421      svn_error_clear(err);
422    }
423  else if (err)
424    return svn_error_trace(err);
425  else if (performed_edit)
426    *performed_edit = TRUE;
427
428  return SVN_NO_ERROR;
429}
430
431
432/* Maximum line length for the prompt string. */
433#define MAX_PROMPT_WIDTH 70
434
435/* Description of a resolver option */
436typedef struct resolver_option_t
437{
438  const char *code;        /* one or two characters */
439  const char *short_desc;  /* label in prompt (localized) */
440  const char *long_desc;   /* longer description (localized) */
441  svn_wc_conflict_choice_t choice;  /* or -1 if not a simple choice */
442} resolver_option_t;
443
444/* Resolver options for a text conflict */
445/* (opt->code == "" causes a blank line break in help_string()) */
446static const resolver_option_t text_conflict_options[] =
447{
448  /* Translators: keep long_desc below 70 characters (wrap with a left
449     margin of 9 spaces if needed); don't translate the words within square
450     brackets. */
451  { "e",  N_("edit file"),        N_("change merged file in an editor"
452                                     "  [edit]"),
453                                  -1 },
454  { "df", N_("show diff"),        N_("show all changes made to merged file"),
455                                  -1 },
456  { "r",  N_("mark resolved"),   N_("accept merged version of file"),
457                                  svn_wc_conflict_choose_merged },
458  { "",   "",                     "", svn_wc_conflict_choose_unspecified },
459  { "dc", N_("display conflict"), N_("show all conflicts "
460                                     "(ignoring merged version)"), -1 },
461  { "mc", N_("my side of conflict"), N_("accept my version for all conflicts "
462                                        "(same)  [mine-conflict]"),
463                                  svn_wc_conflict_choose_mine_conflict },
464  { "tc", N_("their side of conflict"), N_("accept their version for all "
465                                           "conflicts (same)"
466                                           "  [theirs-conflict]"),
467                                  svn_wc_conflict_choose_theirs_conflict },
468  { "",   "",                     "", svn_wc_conflict_choose_unspecified },
469  { "mf", N_("my version"),       N_("accept my version of entire file (even "
470                                     "non-conflicts)  [mine-full]"),
471                                  svn_wc_conflict_choose_mine_full },
472  { "tf", N_("their version"),    N_("accept their version of entire file "
473                                     "(same)  [theirs-full]"),
474                                  svn_wc_conflict_choose_theirs_full },
475  { "",   "",                     "", svn_wc_conflict_choose_unspecified },
476  { "m",  N_("merge"),            N_("use internal merge tool to resolve "
477                                     "conflict"), -1 },
478  { "l",  N_("launch tool"),      N_("launch external tool to resolve "
479                                     "conflict  [launch]"), -1 },
480  { "p",  N_("postpone"),         N_("mark the conflict to be resolved later"
481                                     "  [postpone]"),
482                                  svn_wc_conflict_choose_postpone },
483  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
484                                  svn_wc_conflict_choose_postpone },
485  { "s",  N_("show all options"), N_("show this list (also 'h', '?')"), -1 },
486  { NULL }
487};
488
489/* Resolver options for a property conflict */
490static const resolver_option_t prop_conflict_options[] =
491{
492  { "mf", N_("my version"),       N_("accept my version of entire property (even "
493                                     "non-conflicts)  [mine-full]"),
494                                  svn_wc_conflict_choose_mine_full },
495  { "tf", N_("their version"),    N_("accept their version of entire property "
496                                     "(same)  [theirs-full]"),
497                                  svn_wc_conflict_choose_theirs_full },
498  { "dc", N_("display conflict"), N_("show conflicts in this property"), -1 },
499  { "e",  N_("edit property"),    N_("change merged property value in an editor"
500                                     "  [edit]"), -1 },
501  { "r",  N_("mark resolved"),    N_("accept edited version of property"),
502                                  svn_wc_conflict_choose_merged },
503  { "p",  N_("postpone"),         N_("mark the conflict to be resolved later"
504                                     "  [postpone]"),
505                                  svn_wc_conflict_choose_postpone },
506  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
507                                  svn_wc_conflict_choose_postpone },
508  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
509  { NULL }
510};
511
512/* Resolver options for a tree conflict */
513static const resolver_option_t tree_conflict_options[] =
514{
515  { "r",  N_("mark resolved"),    N_("accept current working copy state"),
516                                  svn_wc_conflict_choose_merged },
517  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
518                                  svn_wc_conflict_choose_postpone },
519  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
520                                  svn_wc_conflict_choose_postpone },
521  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
522  { NULL }
523};
524
525static const resolver_option_t tree_conflict_options_update_moved_away[] =
526{
527  { "mc", N_("apply update (recommended)"),
528                                  N_("apply update to the move destination"
529                                     "  [mine-conflict]"),
530                                  svn_wc_conflict_choose_mine_conflict },
531  { "r",  N_("discard update (breaks move)"), N_("discard update, mark "
532                                                 "resolved, the move will "
533                                                 "will become a copy"),
534                                  svn_wc_conflict_choose_merged },
535  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
536                                  svn_wc_conflict_choose_postpone },
537  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
538                                  svn_wc_conflict_choose_postpone },
539  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
540  { NULL }
541};
542
543static const resolver_option_t tree_conflict_options_update_edit_moved_away[] =
544{
545  { "mc", N_("apply update to move destination"),
546                                  N_("apply incoming update to move destination"
547                                     "  [mine-conflict]"),
548                                  svn_wc_conflict_choose_mine_conflict },
549  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
550                                  svn_wc_conflict_choose_postpone },
551  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
552                                  svn_wc_conflict_choose_postpone },
553  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
554  { NULL }
555};
556
557static const resolver_option_t tree_conflict_options_update_deleted[] =
558{
559  { "mc", N_("keep affected local moves"), N_("keep any local moves affected "
560                                              "by this deletion  [mine-conflict]"),
561                                  svn_wc_conflict_choose_mine_conflict },
562  { "r",  N_("mark resolved (breaks moves)"),  N_("mark resolved, any affected "
563                                                  "moves will become copies"),
564                                  svn_wc_conflict_choose_merged },
565  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
566                                  svn_wc_conflict_choose_postpone },
567  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
568                                  svn_wc_conflict_choose_postpone },
569  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
570  { NULL }
571};
572
573static const resolver_option_t tree_conflict_options_update_replaced[] =
574{
575  { "mc", N_("keep affected local moves"), N_("keep any moves affected by this "
576                                              "replacement  [mine-conflict]"),
577                                  svn_wc_conflict_choose_mine_conflict },
578  { "r",  N_("mark resolved (breaks moves)"), N_("mark resolved (any affected "
579                                                 "moves will become copies)"),
580                                  svn_wc_conflict_choose_merged },
581  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
582                                  svn_wc_conflict_choose_postpone },
583  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
584                                  svn_wc_conflict_choose_postpone },
585  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
586  { NULL }
587};
588
589
590/* Return a pointer to the option description in OPTIONS matching the
591 * one- or two-character OPTION_CODE.  Return NULL if not found. */
592static const resolver_option_t *
593find_option(const resolver_option_t *options,
594            const char *option_code)
595{
596  const resolver_option_t *opt;
597
598  for (opt = options; opt->code; opt++)
599    {
600      /* Ignore code "" (blank lines) which is not a valid answer. */
601      if (opt->code[0] && strcmp(opt->code, option_code) == 0)
602        return opt;
603    }
604  return NULL;
605}
606
607/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
608 * non-null, select only the options whose codes are mentioned in it. */
609static const char *
610prompt_string(const resolver_option_t *options,
611              const char *const *option_codes,
612              apr_pool_t *pool)
613{
614  const char *result = _("Select:");
615  int left_margin = svn_utf_cstring_utf8_width(result);
616  const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
617  int this_line_len = left_margin;
618  svn_boolean_t first = TRUE;
619
620  while (1)
621    {
622      const resolver_option_t *opt;
623      const char *s;
624      int slen;
625
626      if (option_codes)
627        {
628          if (! *option_codes)
629            break;
630          opt = find_option(options, *option_codes++);
631        }
632      else
633        {
634          opt = options++;
635          if (! opt->code)
636            break;
637        }
638
639      if (! first)
640        result = apr_pstrcat(pool, result, ",", (char *)NULL);
641      s = apr_psprintf(pool, _(" (%s) %s"),
642                       opt->code, _(opt->short_desc));
643      slen = svn_utf_cstring_utf8_width(s);
644      /* Break the line if adding the next option would make it too long */
645      if (this_line_len + slen > MAX_PROMPT_WIDTH)
646        {
647          result = apr_pstrcat(pool, result, line_sep, (char *)NULL);
648          this_line_len = left_margin;
649        }
650      result = apr_pstrcat(pool, result, s, (char *)NULL);
651      this_line_len += slen;
652      first = FALSE;
653    }
654  return apr_pstrcat(pool, result, ": ", (char *)NULL);
655}
656
657/* Return a help string listing the OPTIONS. */
658static const char *
659help_string(const resolver_option_t *options,
660            apr_pool_t *pool)
661{
662  const char *result = "";
663  const resolver_option_t *opt;
664
665  for (opt = options; opt->code; opt++)
666    {
667      /* Append a line describing OPT, or a blank line if its code is "". */
668      if (opt->code[0])
669        {
670          const char *s = apr_psprintf(pool, "  (%s)", opt->code);
671
672          result = apr_psprintf(pool, "%s%-6s - %s\n",
673                                result, s, _(opt->long_desc));
674        }
675      else
676        {
677          result = apr_pstrcat(pool, result, "\n", (char *)NULL);
678        }
679    }
680  result = apr_pstrcat(pool, result,
681                       _("Words in square brackets are the corresponding "
682                         "--accept option arguments.\n"),
683                       (char *)NULL);
684  return result;
685}
686
687/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
688 * in OPTIONS_TO_SHOW if that is non-null.  Set *OPT to point to the chosen
689 * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
690 * NULL if the answer was not one of them.
691 *
692 * If the answer is the (globally recognized) 'help' option, then display
693 * the help (on stderr) and return with *OPT == NULL.
694 */
695static svn_error_t *
696prompt_user(const resolver_option_t **opt,
697            const resolver_option_t *conflict_options,
698            const char *const *options_to_show,
699            void *prompt_baton,
700            apr_pool_t *scratch_pool)
701{
702  const char *prompt
703    = prompt_string(conflict_options, options_to_show, scratch_pool);
704  const char *answer;
705
706  SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
707  if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
708    {
709      SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
710                                  help_string(conflict_options,
711                                              scratch_pool)));
712      *opt = NULL;
713    }
714  else
715    {
716      *opt = find_option(conflict_options, answer);
717      if (! *opt)
718        {
719          SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
720                                      _("Unrecognized option.\n\n")));
721        }
722    }
723  return SVN_NO_ERROR;
724}
725
726/* Ask the user what to do about the text conflict described by DESC.
727 * Return the answer in RESULT. B is the conflict baton for this
728 * conflict resolution session.
729 * SCRATCH_POOL is used for temporary allocations. */
730static svn_error_t *
731handle_text_conflict(svn_wc_conflict_result_t *result,
732                     const svn_wc_conflict_description2_t *desc,
733                     svn_cl__interactive_conflict_baton_t *b,
734                     apr_pool_t *scratch_pool)
735{
736  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
737  svn_boolean_t diff_allowed = FALSE;
738  /* Have they done something that might have affected the merged
739     file (so that we need to save a .edited copy)? */
740  svn_boolean_t performed_edit = FALSE;
741  /* Have they done *something* (edit, look at diff, etc) to
742     give them a rational basis for choosing (r)esolved? */
743  svn_boolean_t knows_something = FALSE;
744
745  SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text);
746
747  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
748                              _("Conflict discovered in file '%s'.\n"),
749                              svn_cl__local_style_skip_ancestor(
750                                b->path_prefix, desc->local_abspath,
751                                scratch_pool)));
752
753  /* Diffing can happen between base and merged, to show conflict
754     markers to the user (this is the typical 3-way merge
755     scenario), or if no base is available, we can show a diff
756     between mine and theirs. */
757  if ((desc->merged_file && desc->base_abspath)
758      || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
759    diff_allowed = TRUE;
760
761  while (TRUE)
762    {
763      const char *options[ARRAY_LEN(text_conflict_options)];
764      const char **next_option = options;
765      const resolver_option_t *opt;
766
767      svn_pool_clear(iterpool);
768
769      *next_option++ = "p";
770      if (diff_allowed)
771        {
772          *next_option++ = "df";
773          *next_option++ = "e";
774          *next_option++ = "m";
775
776          if (knows_something)
777            *next_option++ = "r";
778
779          if (! desc->is_binary)
780            {
781              *next_option++ = "mc";
782              *next_option++ = "tc";
783            }
784        }
785      else
786        {
787          if (knows_something)
788            *next_option++ = "r";
789          *next_option++ = "mf";
790          *next_option++ = "tf";
791        }
792      *next_option++ = "s";
793      *next_option++ = NULL;
794
795      SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->pb,
796                          iterpool));
797      if (! opt)
798        continue;
799
800      if (strcmp(opt->code, "q") == 0)
801        {
802          result->choice = opt->choice;
803          b->accept_which = svn_cl__accept_postpone;
804          b->quit = TRUE;
805          break;
806        }
807      else if (strcmp(opt->code, "s") == 0)
808        {
809          SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
810                                      help_string(text_conflict_options,
811                                                  iterpool)));
812        }
813      else if (strcmp(opt->code, "dc") == 0)
814        {
815          if (desc->is_binary)
816            {
817              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
818                                          _("Invalid option; cannot "
819                                            "display conflicts for a "
820                                            "binary file.\n\n")));
821              continue;
822            }
823          else if (! (desc->my_abspath && desc->base_abspath &&
824                      desc->their_abspath))
825            {
826              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
827                                          _("Invalid option; original "
828                                            "files not available.\n\n")));
829              continue;
830            }
831          SVN_ERR(show_conflicts(desc, iterpool));
832          knows_something = TRUE;
833        }
834      else if (strcmp(opt->code, "df") == 0)
835        {
836          if (! diff_allowed)
837            {
838              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
839                             _("Invalid option; there's no "
840                                "merged version to diff.\n\n")));
841              continue;
842            }
843
844          SVN_ERR(show_diff(desc, b->path_prefix, iterpool));
845          knows_something = TRUE;
846        }
847      else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
848        {
849          SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool));
850          if (performed_edit)
851            knows_something = TRUE;
852        }
853      else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
854               strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
855        {
856          if (desc->kind != svn_wc_conflict_kind_text)
857            {
858              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
859                                          _("Invalid option; can only "
860                                            "resolve text conflicts with "
861                                            "the internal merge tool."
862                                            "\n\n")));
863              continue;
864            }
865
866          if (desc->base_abspath && desc->their_abspath &&
867              desc->my_abspath && desc->merged_file)
868            {
869              svn_boolean_t remains_in_conflict;
870
871              SVN_ERR(svn_cl__merge_file(desc->base_abspath,
872                                         desc->their_abspath,
873                                         desc->my_abspath,
874                                         desc->merged_file,
875                                         desc->local_abspath,
876                                         b->path_prefix,
877                                         b->editor_cmd,
878                                         b->config,
879                                         &remains_in_conflict,
880                                         iterpool));
881              knows_something = !remains_in_conflict;
882            }
883          else
884            SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
885                                        _("Invalid option.\n\n")));
886        }
887      else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
888        {
889          /* ### This check should be earlier as it's nasty to offer an option
890           *     and then when the user chooses it say 'Invalid option'. */
891          /* ### 'merged_file' shouldn't be necessary *before* we launch the
892           *     resolver: it should be the *result* of doing so. */
893          if (desc->base_abspath && desc->their_abspath &&
894              desc->my_abspath && desc->merged_file)
895            {
896              SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool));
897              if (performed_edit)
898                knows_something = TRUE;
899            }
900          else
901            SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
902                                        _("Invalid option.\n\n")));
903        }
904      else if (opt->choice != -1)
905        {
906          if ((opt->choice == svn_wc_conflict_choose_mine_conflict
907               || opt->choice == svn_wc_conflict_choose_theirs_conflict)
908              && desc->is_binary)
909            {
910              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
911                                          _("Invalid option; cannot choose "
912                                            "based on conflicts in a "
913                                            "binary file.\n\n")));
914              continue;
915            }
916
917          /* We only allow the user accept the merged version of
918             the file if they've edited it, or at least looked at
919             the diff. */
920          if (opt->choice == svn_wc_conflict_choose_merged
921              && ! knows_something)
922            {
923              SVN_ERR(svn_cmdline_fprintf(
924                        stderr, iterpool,
925                        _("Invalid option; use diff/edit/merge/launch "
926                          "before choosing 'mark resolved'.\n\n")));
927              continue;
928            }
929
930          result->choice = opt->choice;
931          if (performed_edit)
932            result->save_merged = TRUE;
933          break;
934        }
935    }
936  svn_pool_destroy(iterpool);
937
938  return SVN_NO_ERROR;
939}
940
941/* Ask the user what to do about the property conflict described by DESC.
942 * Return the answer in RESULT. B is the conflict baton for this
943 * conflict resolution session.
944 * SCRATCH_POOL is used for temporary allocations. */
945static svn_error_t *
946handle_prop_conflict(svn_wc_conflict_result_t *result,
947                     const svn_wc_conflict_description2_t *desc,
948                     svn_cl__interactive_conflict_baton_t *b,
949                     apr_pool_t *result_pool,
950                     apr_pool_t *scratch_pool)
951{
952  apr_pool_t *iterpool;
953  const char *message;
954  const char *merged_file_path = NULL;
955  svn_boolean_t resolved_allowed = FALSE;
956
957  /* ### Work around a historical bug in the provider: the path to the
958   *     conflict description file was put in the 'theirs' field, and
959   *     'theirs' was put in the 'merged' field. */
960  ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file;
961  ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL;
962
963  SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property);
964
965  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
966                              _("Conflict for property '%s' discovered"
967                                " on '%s'.\n"),
968                              desc->property_name,
969                              svn_cl__local_style_skip_ancestor(
970                                b->path_prefix, desc->local_abspath,
971                                scratch_pool)));
972
973  SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc,
974                                                               scratch_pool));
975  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message));
976
977  iterpool = svn_pool_create(scratch_pool);
978  while (TRUE)
979    {
980      const resolver_option_t *opt;
981      const char *options[ARRAY_LEN(prop_conflict_options)];
982      const char **next_option = options;
983
984      *next_option++ = "p";
985      *next_option++ = "mf";
986      *next_option++ = "tf";
987      *next_option++ = "dc";
988      *next_option++ = "e";
989      if (resolved_allowed)
990        *next_option++ = "r";
991      *next_option++ = "q";
992      *next_option++ = "h";
993      *next_option++ = NULL;
994
995      svn_pool_clear(iterpool);
996
997      SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb,
998                          iterpool));
999      if (! opt)
1000        continue;
1001
1002      if (strcmp(opt->code, "q") == 0)
1003        {
1004          result->choice = opt->choice;
1005          b->accept_which = svn_cl__accept_postpone;
1006          b->quit = TRUE;
1007          break;
1008        }
1009      else if (strcmp(opt->code, "dc") == 0)
1010        {
1011          SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool));
1012        }
1013      else if (strcmp(opt->code, "e") == 0)
1014        {
1015          SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b,
1016                                     result_pool, scratch_pool));
1017          resolved_allowed = (merged_file_path != NULL);
1018        }
1019      else if (strcmp(opt->code, "r") == 0)
1020        {
1021          if (! resolved_allowed)
1022            {
1023              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1024                             _("Invalid option; please edit the property "
1025                               "first.\n\n")));
1026              continue;
1027            }
1028
1029          result->merged_file = merged_file_path;
1030          result->choice = svn_wc_conflict_choose_merged;
1031          break;
1032        }
1033      else if (opt->choice != -1)
1034        {
1035          result->choice = opt->choice;
1036          break;
1037        }
1038    }
1039  svn_pool_destroy(iterpool);
1040
1041  return SVN_NO_ERROR;
1042}
1043
1044/* Ask the user what to do about the tree conflict described by DESC.
1045 * Return the answer in RESULT. B is the conflict baton for this
1046 * conflict resolution session.
1047 * SCRATCH_POOL is used for temporary allocations. */
1048static svn_error_t *
1049handle_tree_conflict(svn_wc_conflict_result_t *result,
1050                     const svn_wc_conflict_description2_t *desc,
1051                     svn_cl__interactive_conflict_baton_t *b,
1052                     apr_pool_t *scratch_pool)
1053{
1054  const char *readable_desc;
1055  apr_pool_t *iterpool;
1056
1057  SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
1058           &readable_desc, desc, scratch_pool));
1059  SVN_ERR(svn_cmdline_fprintf(
1060               stderr, scratch_pool,
1061               _("Tree conflict on '%s'\n   > %s\n"),
1062               svn_cl__local_style_skip_ancestor(b->path_prefix,
1063                                                 desc->local_abspath,
1064                                                 scratch_pool),
1065               readable_desc));
1066
1067  iterpool = svn_pool_create(scratch_pool);
1068  while (1)
1069    {
1070      const resolver_option_t *opt;
1071      const resolver_option_t *tc_opts;
1072
1073      svn_pool_clear(iterpool);
1074
1075      if (desc->operation == svn_wc_operation_update ||
1076          desc->operation == svn_wc_operation_switch)
1077        {
1078          if (desc->reason == svn_wc_conflict_reason_moved_away)
1079            {
1080              if (desc->action == svn_wc_conflict_action_edit)
1081                tc_opts = tree_conflict_options_update_edit_moved_away;
1082              else
1083                tc_opts = tree_conflict_options_update_moved_away;
1084            }
1085          else if (desc->reason == svn_wc_conflict_reason_deleted)
1086            tc_opts = tree_conflict_options_update_deleted;
1087          else if (desc->reason == svn_wc_conflict_reason_replaced)
1088            tc_opts = tree_conflict_options_update_replaced;
1089          else
1090            tc_opts = tree_conflict_options;
1091        }
1092      else
1093        tc_opts = tree_conflict_options;
1094
1095      SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool));
1096      if (! opt)
1097        continue;
1098
1099      if (strcmp(opt->code, "q") == 0)
1100        {
1101          result->choice = opt->choice;
1102          b->accept_which = svn_cl__accept_postpone;
1103          b->quit = TRUE;
1104          break;
1105        }
1106      else if (opt->choice != -1)
1107        {
1108          result->choice = opt->choice;
1109          break;
1110        }
1111    }
1112  svn_pool_destroy(iterpool);
1113
1114  return SVN_NO_ERROR;
1115}
1116
1117/* The body of svn_cl__conflict_func_interactive(). */
1118static svn_error_t *
1119conflict_func_interactive(svn_wc_conflict_result_t **result,
1120                          const svn_wc_conflict_description2_t *desc,
1121                          void *baton,
1122                          apr_pool_t *result_pool,
1123                          apr_pool_t *scratch_pool)
1124{
1125  svn_cl__interactive_conflict_baton_t *b = baton;
1126  svn_error_t *err;
1127
1128  /* Start out assuming we're going to postpone the conflict. */
1129  *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
1130                                          NULL, result_pool);
1131
1132  switch (b->accept_which)
1133    {
1134    case svn_cl__accept_invalid:
1135    case svn_cl__accept_unspecified:
1136      /* No (or no valid) --accept option, fall through to prompting. */
1137      break;
1138    case svn_cl__accept_postpone:
1139      (*result)->choice = svn_wc_conflict_choose_postpone;
1140      return SVN_NO_ERROR;
1141    case svn_cl__accept_base:
1142      (*result)->choice = svn_wc_conflict_choose_base;
1143      return SVN_NO_ERROR;
1144    case svn_cl__accept_working:
1145      /* If the caller didn't merge the property values, then I guess
1146       * 'choose working' means 'choose mine'... */
1147      if (! desc->merged_file)
1148        (*result)->merged_file = desc->my_abspath;
1149      (*result)->choice = svn_wc_conflict_choose_merged;
1150      return SVN_NO_ERROR;
1151    case svn_cl__accept_mine_conflict:
1152      (*result)->choice = svn_wc_conflict_choose_mine_conflict;
1153      return SVN_NO_ERROR;
1154    case svn_cl__accept_theirs_conflict:
1155      (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
1156      return SVN_NO_ERROR;
1157    case svn_cl__accept_mine_full:
1158      (*result)->choice = svn_wc_conflict_choose_mine_full;
1159      return SVN_NO_ERROR;
1160    case svn_cl__accept_theirs_full:
1161      (*result)->choice = svn_wc_conflict_choose_theirs_full;
1162      return SVN_NO_ERROR;
1163    case svn_cl__accept_edit:
1164      if (desc->merged_file)
1165        {
1166          if (b->external_failed)
1167            {
1168              (*result)->choice = svn_wc_conflict_choose_postpone;
1169              return SVN_NO_ERROR;
1170            }
1171
1172          err = svn_cmdline__edit_file_externally(desc->merged_file,
1173                                                  b->editor_cmd, b->config,
1174                                                  scratch_pool);
1175          if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
1176            {
1177              SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1178                                          err->message ? err->message :
1179                                          _("No editor found;"
1180                                            " leaving all conflicts.")));
1181              svn_error_clear(err);
1182              b->external_failed = TRUE;
1183            }
1184          else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
1185            {
1186              SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1187                                          err->message ? err->message :
1188                                          _("Error running editor;"
1189                                            " leaving all conflicts.")));
1190              svn_error_clear(err);
1191              b->external_failed = TRUE;
1192            }
1193          else if (err)
1194            return svn_error_trace(err);
1195          (*result)->choice = svn_wc_conflict_choose_merged;
1196          return SVN_NO_ERROR;
1197        }
1198      /* else, fall through to prompting. */
1199      break;
1200    case svn_cl__accept_launch:
1201      if (desc->base_abspath && desc->their_abspath
1202          && desc->my_abspath && desc->merged_file)
1203        {
1204          svn_boolean_t remains_in_conflict;
1205
1206          if (b->external_failed)
1207            {
1208              (*result)->choice = svn_wc_conflict_choose_postpone;
1209              return SVN_NO_ERROR;
1210            }
1211
1212          err = svn_cl__merge_file_externally(desc->base_abspath,
1213                                              desc->their_abspath,
1214                                              desc->my_abspath,
1215                                              desc->merged_file,
1216                                              desc->local_abspath,
1217                                              b->config,
1218                                              &remains_in_conflict,
1219                                              scratch_pool);
1220          if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
1221            {
1222              SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1223                                          err->message ? err->message :
1224                                          _("No merge tool found;"
1225                                            " leaving all conflicts.")));
1226              b->external_failed = TRUE;
1227              return svn_error_trace(err);
1228            }
1229          else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
1230            {
1231              SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1232                                          err->message ? err->message :
1233                                          _("Error running merge tool;"
1234                                            " leaving all conflicts.")));
1235              b->external_failed = TRUE;
1236              return svn_error_trace(err);
1237            }
1238          else if (err)
1239            return svn_error_trace(err);
1240
1241          if (remains_in_conflict)
1242            (*result)->choice = svn_wc_conflict_choose_postpone;
1243          else
1244            (*result)->choice = svn_wc_conflict_choose_merged;
1245          return SVN_NO_ERROR;
1246        }
1247      /* else, fall through to prompting. */
1248      break;
1249    }
1250
1251  /* We're in interactive mode and either the user gave no --accept
1252     option or the option did not apply; let's prompt. */
1253
1254  /* Handle the most common cases, which is either:
1255
1256     Conflicting edits on a file's text, or
1257     Conflicting edits on a property.
1258  */
1259  if (((desc->kind == svn_wc_conflict_kind_text)
1260       && (desc->action == svn_wc_conflict_action_edit)
1261       && (desc->reason == svn_wc_conflict_reason_edited)))
1262    SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool));
1263  else if (desc->kind == svn_wc_conflict_kind_property)
1264    SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool));
1265  else if (desc->kind == svn_wc_conflict_kind_tree)
1266    SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool));
1267
1268  else /* other types of conflicts -- do nothing about them. */
1269    {
1270      (*result)->choice = svn_wc_conflict_choose_postpone;
1271    }
1272
1273  return SVN_NO_ERROR;
1274}
1275
1276svn_error_t *
1277svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
1278                                  const svn_wc_conflict_description2_t *desc,
1279                                  void *baton,
1280                                  apr_pool_t *result_pool,
1281                                  apr_pool_t *scratch_pool)
1282{
1283  svn_cl__interactive_conflict_baton_t *b = baton;
1284
1285  SVN_ERR(conflict_func_interactive(result, desc, baton,
1286                                    result_pool, scratch_pool));
1287
1288  /* If we are resolving a conflict, adjust the summary of conflicts. */
1289  if ((*result)->choice != svn_wc_conflict_choose_postpone)
1290    {
1291      const char *local_path
1292        = svn_cl__local_style_skip_ancestor(
1293            b->path_prefix, desc->local_abspath, scratch_pool);
1294
1295      svn_cl__conflict_stats_resolved(b->conflict_stats, local_path,
1296                                             desc->kind);
1297    }
1298  return SVN_NO_ERROR;
1299}
1300