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