]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svn/conflict-callbacks.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / svn / conflict-callbacks.c
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 \f
52
53 struct 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
65 svn_error_t *
66 svn_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
94 svn_cl__accept_t
95 svn_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. */
132 static svn_error_t *
133 show_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. */
208 static svn_error_t *
209 show_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  */
250 static svn_error_t *
251 merge_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  */
311 static svn_error_t *
312 show_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. */
336 static svn_error_t *
337 open_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. */
374 static svn_error_t *
375 edit_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 */
407 typedef 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()) */
418 static 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. */
468 static 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 */
490 static 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 */
516 static 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
529 static 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
544 static 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. */
561 static const resolver_option_t *
562 find_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. */
578 static const char *
579 prompt_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. */
627 static const char *
628 help_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  */
664 static svn_error_t *
665 prompt_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. */
699 static svn_error_t *
700 handle_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. */
1021 static svn_error_t *
1022 handle_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. */
1126 static svn_error_t *
1127 handle_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(). */
1194 static svn_error_t *
1195 conflict_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
1347 svn_error_t *
1348 svn_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 }