]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - contrib/subversion/subversion/svn/conflict-callbacks.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.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 property (even "
493                                      "non-conflicts)  [mine-full]"),
494                                   svn_wc_conflict_choose_mine_full },
495   { "tf", N_("their version"),    N_("accept their version of entire property "
496                                      "(same)  [theirs-full]"),
497                                   svn_wc_conflict_choose_theirs_full },
498   { "dc", N_("display conflict"), N_("show conflicts in this property"), -1 },
499   { "e",  N_("edit property"),    N_("change merged property value in an editor"
500                                      "  [edit]"), -1 },
501   { "r",  N_("mark resolved"),    N_("accept edited version of property"),
502                                   svn_wc_conflict_choose_merged },
503   { "p",  N_("postpone"),         N_("mark the conflict to be resolved later"
504                                      "  [postpone]"),
505                                   svn_wc_conflict_choose_postpone },
506   { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
507                                   svn_wc_conflict_choose_postpone },
508   { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
509   { NULL }
510 };
511
512 /* Resolver options for a tree conflict */
513 static const resolver_option_t tree_conflict_options[] =
514 {
515   { "r",  N_("mark resolved"),    N_("accept current working copy state"),
516                                   svn_wc_conflict_choose_merged },
517   { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
518                                   svn_wc_conflict_choose_postpone },
519   { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
520                                   svn_wc_conflict_choose_postpone },
521   { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
522   { NULL }
523 };
524
525 static const resolver_option_t tree_conflict_options_update_moved_away[] =
526 {
527   { "mc", N_("apply update (recommended)"),
528                                   N_("apply update to the move destination"
529                                      "  [mine-conflict]"),
530                                   svn_wc_conflict_choose_mine_conflict },
531   { "r",  N_("discard update (breaks move)"), N_("discard update, mark "
532                                                  "resolved, the move will "
533                                                  "will become a copy"),
534                                   svn_wc_conflict_choose_merged },
535   { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
536                                   svn_wc_conflict_choose_postpone },
537   { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
538                                   svn_wc_conflict_choose_postpone },
539   { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
540   { NULL }
541 };
542
543 static const resolver_option_t tree_conflict_options_update_edit_moved_away[] =
544 {
545   { "mc", N_("apply update to move destination"),
546                                   N_("apply incoming update to move destination"
547                                      "  [mine-conflict]"),
548                                   svn_wc_conflict_choose_mine_conflict },
549   { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
550                                   svn_wc_conflict_choose_postpone },
551   { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
552                                   svn_wc_conflict_choose_postpone },
553   { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
554   { NULL }
555 };
556
557 static const resolver_option_t tree_conflict_options_update_deleted[] =
558 {
559   { "mc", N_("keep affected local moves"), N_("keep any local moves affected "
560                                               "by this deletion  [mine-conflict]"),
561                                   svn_wc_conflict_choose_mine_conflict },
562   { "r",  N_("mark resolved (breaks moves)"),  N_("mark resolved, any affected "
563                                                   "moves will become copies"),
564                                   svn_wc_conflict_choose_merged },
565   { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
566                                   svn_wc_conflict_choose_postpone },
567   { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
568                                   svn_wc_conflict_choose_postpone },
569   { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
570   { NULL }
571 };
572
573 static const resolver_option_t tree_conflict_options_update_replaced[] =
574 {
575   { "mc", N_("keep affected local moves"), N_("keep any moves affected by this "
576                                               "replacement  [mine-conflict]"),
577                                   svn_wc_conflict_choose_mine_conflict },
578   { "r",  N_("mark resolved (breaks moves)"), N_("mark resolved (any affected "
579                                                  "moves will become copies)"),
580                                   svn_wc_conflict_choose_merged },
581   { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
582                                   svn_wc_conflict_choose_postpone },
583   { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
584                                   svn_wc_conflict_choose_postpone },
585   { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
586   { NULL }
587 };
588
589
590 /* Return a pointer to the option description in OPTIONS matching the
591  * one- or two-character OPTION_CODE.  Return NULL if not found. */
592 static const resolver_option_t *
593 find_option(const resolver_option_t *options,
594             const char *option_code)
595 {
596   const resolver_option_t *opt;
597
598   for (opt = options; opt->code; opt++)
599     {
600       /* Ignore code "" (blank lines) which is not a valid answer. */
601       if (opt->code[0] && strcmp(opt->code, option_code) == 0)
602         return opt;
603     }
604   return NULL;
605 }
606
607 /* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
608  * non-null, select only the options whose codes are mentioned in it. */
609 static const char *
610 prompt_string(const resolver_option_t *options,
611               const char *const *option_codes,
612               apr_pool_t *pool)
613 {
614   const char *result = _("Select:");
615   int left_margin = svn_utf_cstring_utf8_width(result);
616   const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
617   int this_line_len = left_margin;
618   svn_boolean_t first = TRUE;
619
620   while (1)
621     {
622       const resolver_option_t *opt;
623       const char *s;
624       int slen;
625
626       if (option_codes)
627         {
628           if (! *option_codes)
629             break;
630           opt = find_option(options, *option_codes++);
631         }
632       else
633         {
634           opt = options++;
635           if (! opt->code)
636             break;
637         }
638
639       if (! first)
640         result = apr_pstrcat(pool, result, ",", (char *)NULL);
641       s = apr_psprintf(pool, _(" (%s) %s"),
642                        opt->code, _(opt->short_desc));
643       slen = svn_utf_cstring_utf8_width(s);
644       /* Break the line if adding the next option would make it too long */
645       if (this_line_len + slen > MAX_PROMPT_WIDTH)
646         {
647           result = apr_pstrcat(pool, result, line_sep, (char *)NULL);
648           this_line_len = left_margin;
649         }
650       result = apr_pstrcat(pool, result, s, (char *)NULL);
651       this_line_len += slen;
652       first = FALSE;
653     }
654   return apr_pstrcat(pool, result, ": ", (char *)NULL);
655 }
656
657 /* Return a help string listing the OPTIONS. */
658 static const char *
659 help_string(const resolver_option_t *options,
660             apr_pool_t *pool)
661 {
662   const char *result = "";
663   const resolver_option_t *opt;
664
665   for (opt = options; opt->code; opt++)
666     {
667       /* Append a line describing OPT, or a blank line if its code is "". */
668       if (opt->code[0])
669         {
670           const char *s = apr_psprintf(pool, "  (%s)", opt->code);
671
672           result = apr_psprintf(pool, "%s%-6s - %s\n",
673                                 result, s, _(opt->long_desc));
674         }
675       else
676         {
677           result = apr_pstrcat(pool, result, "\n", (char *)NULL);
678         }
679     }
680   result = apr_pstrcat(pool, result,
681                        _("Words in square brackets are the corresponding "
682                          "--accept option arguments.\n"),
683                        (char *)NULL);
684   return result;
685 }
686
687 /* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
688  * in OPTIONS_TO_SHOW if that is non-null.  Set *OPT to point to the chosen
689  * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
690  * NULL if the answer was not one of them.
691  *
692  * If the answer is the (globally recognized) 'help' option, then display
693  * the help (on stderr) and return with *OPT == NULL.
694  */
695 static svn_error_t *
696 prompt_user(const resolver_option_t **opt,
697             const resolver_option_t *conflict_options,
698             const char *const *options_to_show,
699             void *prompt_baton,
700             apr_pool_t *scratch_pool)
701 {
702   const char *prompt
703     = prompt_string(conflict_options, options_to_show, scratch_pool);
704   const char *answer;
705
706   SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
707   if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
708     {
709       SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
710                                   help_string(conflict_options,
711                                               scratch_pool)));
712       *opt = NULL;
713     }
714   else
715     {
716       *opt = find_option(conflict_options, answer);
717       if (! *opt)
718         {
719           SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
720                                       _("Unrecognized option.\n\n")));
721         }
722     }
723   return SVN_NO_ERROR;
724 }
725
726 /* Ask the user what to do about the text conflict described by DESC.
727  * Return the answer in RESULT. B is the conflict baton for this
728  * conflict resolution session.
729  * SCRATCH_POOL is used for temporary allocations. */
730 static svn_error_t *
731 handle_text_conflict(svn_wc_conflict_result_t *result,
732                      const svn_wc_conflict_description2_t *desc,
733                      svn_cl__interactive_conflict_baton_t *b,
734                      apr_pool_t *scratch_pool)
735 {
736   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
737   svn_boolean_t diff_allowed = FALSE;
738   /* Have they done something that might have affected the merged
739      file (so that we need to save a .edited copy)? */
740   svn_boolean_t performed_edit = FALSE;
741   /* Have they done *something* (edit, look at diff, etc) to
742      give them a rational basis for choosing (r)esolved? */
743   svn_boolean_t knows_something = FALSE;
744
745   SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text);
746
747   SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
748                               _("Conflict discovered in file '%s'.\n"),
749                               svn_cl__local_style_skip_ancestor(
750                                 b->path_prefix, desc->local_abspath,
751                                 scratch_pool)));
752
753   /* Diffing can happen between base and merged, to show conflict
754      markers to the user (this is the typical 3-way merge
755      scenario), or if no base is available, we can show a diff
756      between mine and theirs. */
757   if ((desc->merged_file && desc->base_abspath)
758       || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
759     diff_allowed = TRUE;
760
761   while (TRUE)
762     {
763       const char *options[ARRAY_LEN(text_conflict_options)];
764       const char **next_option = options;
765       const resolver_option_t *opt;
766
767       svn_pool_clear(iterpool);
768
769       *next_option++ = "p";
770       if (diff_allowed)
771         {
772           *next_option++ = "df";
773           *next_option++ = "e";
774           *next_option++ = "m";
775
776           if (knows_something)
777             *next_option++ = "r";
778
779           if (! desc->is_binary)
780             {
781               *next_option++ = "mc";
782               *next_option++ = "tc";
783             }
784         }
785       else
786         {
787           if (knows_something)
788             *next_option++ = "r";
789           *next_option++ = "mf";
790           *next_option++ = "tf";
791         }
792       *next_option++ = "s";
793       *next_option++ = NULL;
794
795       SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->pb,
796                           iterpool));
797       if (! opt)
798         continue;
799
800       if (strcmp(opt->code, "q") == 0)
801         {
802           result->choice = opt->choice;
803           b->accept_which = svn_cl__accept_postpone;
804           b->quit = TRUE;
805           break;
806         }
807       else if (strcmp(opt->code, "s") == 0)
808         {
809           SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
810                                       help_string(text_conflict_options,
811                                                   iterpool)));
812         }
813       else if (strcmp(opt->code, "dc") == 0)
814         {
815           if (desc->is_binary)
816             {
817               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
818                                           _("Invalid option; cannot "
819                                             "display conflicts for a "
820                                             "binary file.\n\n")));
821               continue;
822             }
823           else if (! (desc->my_abspath && desc->base_abspath &&
824                       desc->their_abspath))
825             {
826               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
827                                           _("Invalid option; original "
828                                             "files not available.\n\n")));
829               continue;
830             }
831           SVN_ERR(show_conflicts(desc, iterpool));
832           knows_something = TRUE;
833         }
834       else if (strcmp(opt->code, "df") == 0)
835         {
836           if (! diff_allowed)
837             {
838               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
839                              _("Invalid option; there's no "
840                                 "merged version to diff.\n\n")));
841               continue;
842             }
843
844           SVN_ERR(show_diff(desc, b->path_prefix, iterpool));
845           knows_something = TRUE;
846         }
847       else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
848         {
849           SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool));
850           if (performed_edit)
851             knows_something = TRUE;
852         }
853       else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
854                strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
855         {
856           if (desc->kind != svn_wc_conflict_kind_text)
857             {
858               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
859                                           _("Invalid option; can only "
860                                             "resolve text conflicts with "
861                                             "the internal merge tool."
862                                             "\n\n")));
863               continue;
864             }
865
866           if (desc->base_abspath && desc->their_abspath &&
867               desc->my_abspath && desc->merged_file)
868             {
869               svn_boolean_t remains_in_conflict;
870
871               SVN_ERR(svn_cl__merge_file(desc->base_abspath,
872                                          desc->their_abspath,
873                                          desc->my_abspath,
874                                          desc->merged_file,
875                                          desc->local_abspath,
876                                          b->path_prefix,
877                                          b->editor_cmd,
878                                          b->config,
879                                          &remains_in_conflict,
880                                          iterpool));
881               knows_something = !remains_in_conflict;
882             }
883           else
884             SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
885                                         _("Invalid option.\n\n")));
886         }
887       else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
888         {
889           /* ### This check should be earlier as it's nasty to offer an option
890            *     and then when the user chooses it say 'Invalid option'. */
891           /* ### 'merged_file' shouldn't be necessary *before* we launch the
892            *     resolver: it should be the *result* of doing so. */
893           if (desc->base_abspath && desc->their_abspath &&
894               desc->my_abspath && desc->merged_file)
895             {
896               SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool));
897               if (performed_edit)
898                 knows_something = TRUE;
899             }
900           else
901             SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
902                                         _("Invalid option.\n\n")));
903         }
904       else if (opt->choice != -1)
905         {
906           if ((opt->choice == svn_wc_conflict_choose_mine_conflict
907                || opt->choice == svn_wc_conflict_choose_theirs_conflict)
908               && desc->is_binary)
909             {
910               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
911                                           _("Invalid option; cannot choose "
912                                             "based on conflicts in a "
913                                             "binary file.\n\n")));
914               continue;
915             }
916
917           /* We only allow the user accept the merged version of
918              the file if they've edited it, or at least looked at
919              the diff. */
920           if (opt->choice == svn_wc_conflict_choose_merged
921               && ! knows_something)
922             {
923               SVN_ERR(svn_cmdline_fprintf(
924                         stderr, iterpool,
925                         _("Invalid option; use diff/edit/merge/launch "
926                           "before choosing 'mark resolved'.\n\n")));
927               continue;
928             }
929
930           result->choice = opt->choice;
931           if (performed_edit)
932             result->save_merged = TRUE;
933           break;
934         }
935     }
936   svn_pool_destroy(iterpool);
937
938   return SVN_NO_ERROR;
939 }
940
941 /* Ask the user what to do about the property conflict described by DESC.
942  * Return the answer in RESULT. B is the conflict baton for this
943  * conflict resolution session.
944  * SCRATCH_POOL is used for temporary allocations. */
945 static svn_error_t *
946 handle_prop_conflict(svn_wc_conflict_result_t *result,
947                      const svn_wc_conflict_description2_t *desc,
948                      svn_cl__interactive_conflict_baton_t *b,
949                      apr_pool_t *result_pool,
950                      apr_pool_t *scratch_pool)
951 {
952   apr_pool_t *iterpool;
953   const char *message;
954   const char *merged_file_path = NULL;
955   svn_boolean_t resolved_allowed = FALSE;
956
957   /* ### Work around a historical bug in the provider: the path to the
958    *     conflict description file was put in the 'theirs' field, and
959    *     'theirs' was put in the 'merged' field. */
960   ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file;
961   ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL;
962
963   SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property);
964
965   SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
966                               _("Conflict for property '%s' discovered"
967                                 " on '%s'.\n"),
968                               desc->property_name,
969                               svn_cl__local_style_skip_ancestor(
970                                 b->path_prefix, desc->local_abspath,
971                                 scratch_pool)));
972
973   SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc,
974                                                                scratch_pool));
975   SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message));
976
977   iterpool = svn_pool_create(scratch_pool);
978   while (TRUE)
979     {
980       const resolver_option_t *opt;
981       const char *options[ARRAY_LEN(prop_conflict_options)];
982       const char **next_option = options;
983
984       *next_option++ = "p";
985       *next_option++ = "mf";
986       *next_option++ = "tf";
987       *next_option++ = "dc";
988       *next_option++ = "e";
989       if (resolved_allowed)
990         *next_option++ = "r";
991       *next_option++ = "q";
992       *next_option++ = "h";
993       *next_option++ = NULL;
994
995       svn_pool_clear(iterpool);
996
997       SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb,
998                           iterpool));
999       if (! opt)
1000         continue;
1001
1002       if (strcmp(opt->code, "q") == 0)
1003         {
1004           result->choice = opt->choice;
1005           b->accept_which = svn_cl__accept_postpone;
1006           b->quit = TRUE;
1007           break;
1008         }
1009       else if (strcmp(opt->code, "dc") == 0)
1010         {
1011           SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool));
1012         }
1013       else if (strcmp(opt->code, "e") == 0)
1014         {
1015           SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b,
1016                                      result_pool, scratch_pool));
1017           resolved_allowed = (merged_file_path != NULL);
1018         }
1019       else if (strcmp(opt->code, "r") == 0)
1020         {
1021           if (! resolved_allowed)
1022             {
1023               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1024                              _("Invalid option; please edit the property "
1025                                "first.\n\n")));
1026               continue;
1027             }
1028
1029           result->merged_file = merged_file_path;
1030           result->choice = svn_wc_conflict_choose_merged;
1031           break;
1032         }
1033       else if (opt->choice != -1)
1034         {
1035           result->choice = opt->choice;
1036           break;
1037         }
1038     }
1039   svn_pool_destroy(iterpool);
1040
1041   return SVN_NO_ERROR;
1042 }
1043
1044 /* Ask the user what to do about the tree conflict described by DESC.
1045  * Return the answer in RESULT. B is the conflict baton for this
1046  * conflict resolution session.
1047  * SCRATCH_POOL is used for temporary allocations. */
1048 static svn_error_t *
1049 handle_tree_conflict(svn_wc_conflict_result_t *result,
1050                      const svn_wc_conflict_description2_t *desc,
1051                      svn_cl__interactive_conflict_baton_t *b,
1052                      apr_pool_t *scratch_pool)
1053 {
1054   const char *readable_desc;
1055   apr_pool_t *iterpool;
1056
1057   SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
1058            &readable_desc, desc, scratch_pool));
1059   SVN_ERR(svn_cmdline_fprintf(
1060                stderr, scratch_pool,
1061                _("Tree conflict on '%s'\n   > %s\n"),
1062                svn_cl__local_style_skip_ancestor(b->path_prefix,
1063                                                  desc->local_abspath,
1064                                                  scratch_pool),
1065                readable_desc));
1066
1067   iterpool = svn_pool_create(scratch_pool);
1068   while (1)
1069     {
1070       const resolver_option_t *opt;
1071       const resolver_option_t *tc_opts;
1072
1073       svn_pool_clear(iterpool);
1074
1075       if (desc->operation == svn_wc_operation_update ||
1076           desc->operation == svn_wc_operation_switch)
1077         {
1078           if (desc->reason == svn_wc_conflict_reason_moved_away)
1079             {
1080               if (desc->action == svn_wc_conflict_action_edit)
1081                 tc_opts = tree_conflict_options_update_edit_moved_away;
1082               else
1083                 tc_opts = tree_conflict_options_update_moved_away;
1084             }
1085           else if (desc->reason == svn_wc_conflict_reason_deleted)
1086             tc_opts = tree_conflict_options_update_deleted;
1087           else if (desc->reason == svn_wc_conflict_reason_replaced)
1088             tc_opts = tree_conflict_options_update_replaced;
1089           else
1090             tc_opts = tree_conflict_options;
1091         }
1092       else
1093         tc_opts = tree_conflict_options;
1094
1095       SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool));
1096       if (! opt)
1097         continue;
1098
1099       if (strcmp(opt->code, "q") == 0)
1100         {
1101           result->choice = opt->choice;
1102           b->accept_which = svn_cl__accept_postpone;
1103           b->quit = TRUE;
1104           break;
1105         }
1106       else if (opt->choice != -1)
1107         {
1108           result->choice = opt->choice;
1109           break;
1110         }
1111     }
1112   svn_pool_destroy(iterpool);
1113
1114   return SVN_NO_ERROR;
1115 }
1116
1117 /* The body of svn_cl__conflict_func_interactive(). */
1118 static svn_error_t *
1119 conflict_func_interactive(svn_wc_conflict_result_t **result,
1120                           const svn_wc_conflict_description2_t *desc,
1121                           void *baton,
1122                           apr_pool_t *result_pool,
1123                           apr_pool_t *scratch_pool)
1124 {
1125   svn_cl__interactive_conflict_baton_t *b = baton;
1126   svn_error_t *err;
1127
1128   /* Start out assuming we're going to postpone the conflict. */
1129   *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
1130                                           NULL, result_pool);
1131
1132   switch (b->accept_which)
1133     {
1134     case svn_cl__accept_invalid:
1135     case svn_cl__accept_unspecified:
1136       /* No (or no valid) --accept option, fall through to prompting. */
1137       break;
1138     case svn_cl__accept_postpone:
1139       (*result)->choice = svn_wc_conflict_choose_postpone;
1140       return SVN_NO_ERROR;
1141     case svn_cl__accept_base:
1142       (*result)->choice = svn_wc_conflict_choose_base;
1143       return SVN_NO_ERROR;
1144     case svn_cl__accept_working:
1145       /* If the caller didn't merge the property values, then I guess
1146        * 'choose working' means 'choose mine'... */
1147       if (! desc->merged_file)
1148         (*result)->merged_file = desc->my_abspath;
1149       (*result)->choice = svn_wc_conflict_choose_merged;
1150       return SVN_NO_ERROR;
1151     case svn_cl__accept_mine_conflict:
1152       (*result)->choice = svn_wc_conflict_choose_mine_conflict;
1153       return SVN_NO_ERROR;
1154     case svn_cl__accept_theirs_conflict:
1155       (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
1156       return SVN_NO_ERROR;
1157     case svn_cl__accept_mine_full:
1158       (*result)->choice = svn_wc_conflict_choose_mine_full;
1159       return SVN_NO_ERROR;
1160     case svn_cl__accept_theirs_full:
1161       (*result)->choice = svn_wc_conflict_choose_theirs_full;
1162       return SVN_NO_ERROR;
1163     case svn_cl__accept_edit:
1164       if (desc->merged_file)
1165         {
1166           if (b->external_failed)
1167             {
1168               (*result)->choice = svn_wc_conflict_choose_postpone;
1169               return SVN_NO_ERROR;
1170             }
1171
1172           err = svn_cmdline__edit_file_externally(desc->merged_file,
1173                                                   b->editor_cmd, b->config,
1174                                                   scratch_pool);
1175           if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
1176             {
1177               SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1178                                           err->message ? err->message :
1179                                           _("No editor found;"
1180                                             " leaving all conflicts.")));
1181               svn_error_clear(err);
1182               b->external_failed = TRUE;
1183             }
1184           else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
1185             {
1186               SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1187                                           err->message ? err->message :
1188                                           _("Error running editor;"
1189                                             " leaving all conflicts.")));
1190               svn_error_clear(err);
1191               b->external_failed = TRUE;
1192             }
1193           else if (err)
1194             return svn_error_trace(err);
1195           (*result)->choice = svn_wc_conflict_choose_merged;
1196           return SVN_NO_ERROR;
1197         }
1198       /* else, fall through to prompting. */
1199       break;
1200     case svn_cl__accept_launch:
1201       if (desc->base_abspath && desc->their_abspath
1202           && desc->my_abspath && desc->merged_file)
1203         {
1204           svn_boolean_t remains_in_conflict;
1205
1206           if (b->external_failed)
1207             {
1208               (*result)->choice = svn_wc_conflict_choose_postpone;
1209               return SVN_NO_ERROR;
1210             }
1211
1212           err = svn_cl__merge_file_externally(desc->base_abspath,
1213                                               desc->their_abspath,
1214                                               desc->my_abspath,
1215                                               desc->merged_file,
1216                                               desc->local_abspath,
1217                                               b->config,
1218                                               &remains_in_conflict,
1219                                               scratch_pool);
1220           if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
1221             {
1222               SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1223                                           err->message ? err->message :
1224                                           _("No merge tool found;"
1225                                             " leaving all conflicts.")));
1226               b->external_failed = TRUE;
1227               return svn_error_trace(err);
1228             }
1229           else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
1230             {
1231               SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1232                                           err->message ? err->message :
1233                                           _("Error running merge tool;"
1234                                             " leaving all conflicts.")));
1235               b->external_failed = TRUE;
1236               return svn_error_trace(err);
1237             }
1238           else if (err)
1239             return svn_error_trace(err);
1240
1241           if (remains_in_conflict)
1242             (*result)->choice = svn_wc_conflict_choose_postpone;
1243           else
1244             (*result)->choice = svn_wc_conflict_choose_merged;
1245           return SVN_NO_ERROR;
1246         }
1247       /* else, fall through to prompting. */
1248       break;
1249     }
1250
1251   /* We're in interactive mode and either the user gave no --accept
1252      option or the option did not apply; let's prompt. */
1253
1254   /* Handle the most common cases, which is either:
1255
1256      Conflicting edits on a file's text, or
1257      Conflicting edits on a property.
1258   */
1259   if (((desc->kind == svn_wc_conflict_kind_text)
1260        && (desc->action == svn_wc_conflict_action_edit)
1261        && (desc->reason == svn_wc_conflict_reason_edited)))
1262     SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool));
1263   else if (desc->kind == svn_wc_conflict_kind_property)
1264     SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool));
1265   else if (desc->kind == svn_wc_conflict_kind_tree)
1266     SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool));
1267
1268   else /* other types of conflicts -- do nothing about them. */
1269     {
1270       (*result)->choice = svn_wc_conflict_choose_postpone;
1271     }
1272
1273   return SVN_NO_ERROR;
1274 }
1275
1276 svn_error_t *
1277 svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
1278                                   const svn_wc_conflict_description2_t *desc,
1279                                   void *baton,
1280                                   apr_pool_t *result_pool,
1281                                   apr_pool_t *scratch_pool)
1282 {
1283   svn_cl__interactive_conflict_baton_t *b = baton;
1284
1285   SVN_ERR(conflict_func_interactive(result, desc, baton,
1286                                     result_pool, scratch_pool));
1287
1288   /* If we are resolving a conflict, adjust the summary of conflicts. */
1289   if ((*result)->choice != svn_wc_conflict_choose_postpone)
1290     {
1291       const char *local_path
1292         = svn_cl__local_style_skip_ancestor(
1293             b->path_prefix, desc->local_abspath, scratch_pool);
1294
1295       svn_cl__conflict_stats_resolved(b->conflict_stats, local_path,
1296                                              desc->kind);
1297     }
1298   return SVN_NO_ERROR;
1299 }