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