conflict-callbacks.c revision 362181
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
49
50svn_cl__accept_t
51svn_cl__accept_from_word(const char *word)
52{
53  /* Shorthand options are consistent with  svn_cl__conflict_handler(). */
54  if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0
55      || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0)
56    return svn_cl__accept_postpone;
57  if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0)
58    /* ### shorthand? */
59    return svn_cl__accept_base;
60  if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0)
61    /* ### shorthand? */
62    return svn_cl__accept_working;
63  if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0
64      || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0)
65    return svn_cl__accept_mine_conflict;
66  if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0
67      || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0)
68    return svn_cl__accept_theirs_conflict;
69  if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0
70      || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0)
71    return svn_cl__accept_mine_full;
72  if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0
73      || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0)
74    return svn_cl__accept_theirs_full;
75  if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0
76      || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0)
77    return svn_cl__accept_edit;
78  if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0
79      || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0)
80    return svn_cl__accept_launch;
81  if (strcmp(word, SVN_CL__ACCEPT_RECOMMENDED) == 0
82      || strcmp(word, "r") == 0)
83    return svn_cl__accept_recommended;
84  /* word is an invalid action. */
85  return svn_cl__accept_invalid;
86}
87
88
89/* Print on stdout a diff that shows incoming conflicting changes
90 * corresponding to the conflict described in CONFLICT. */
91static svn_error_t *
92show_diff(svn_client_conflict_t *conflict,
93          const char *merged_abspath,
94          const char *path_prefix,
95          svn_cancel_func_t cancel_func,
96          void *cancel_baton,
97          apr_pool_t *pool)
98{
99  const char *path1, *path2;
100  const char *label1, *label2;
101  svn_diff_t *diff;
102  svn_stream_t *output;
103  svn_diff_file_options_t *options;
104  const char *my_abspath;
105  const char *their_abspath;
106
107  SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, NULL,
108                                                &their_abspath,
109                                                conflict, pool, pool));
110  if (merged_abspath)
111    {
112      /* For conflicts recorded by the 'merge' operation, show a diff between
113       * 'mine' (the working version of the file as it appeared before the
114       * 'merge' operation was run) and 'merged' (the version of the file
115       * as it appears after the merge operation).
116       *
117       * For conflicts recorded by the 'update' and 'switch' operations,
118       * show a diff between 'theirs' (the new pristine version of the
119       * file) and 'merged' (the version of the file as it appears with
120       * local changes merged with the new pristine version).
121       *
122       * This way, the diff is always minimal and clearly identifies changes
123       * brought into the working copy by the update/switch/merge operation. */
124      if (svn_client_conflict_get_operation(conflict) == svn_wc_operation_merge)
125        {
126          path1 = my_abspath;
127          label1 = _("MINE");
128        }
129      else
130        {
131          path1 = their_abspath;
132          label1 = _("THEIRS");
133        }
134      path2 = merged_abspath;
135      label2 = _("MERGED");
136    }
137  else
138    {
139      /* There's no merged file, but we can show the
140         difference between mine and theirs. */
141      path1 = their_abspath;
142      label1 = _("THEIRS");
143      path2 = my_abspath;
144      label2 = _("MINE");
145    }
146
147  label1 = apr_psprintf(pool, "%s\t- %s",
148                        svn_cl__local_style_skip_ancestor(
149                          path_prefix, path1, pool), label1);
150  label2 = apr_psprintf(pool, "%s\t- %s",
151                        svn_cl__local_style_skip_ancestor(
152                          path_prefix, path2, pool), label2);
153
154  options = svn_diff_file_options_create(pool);
155  options->ignore_eol_style = TRUE;
156  SVN_ERR(svn_stream_for_stdout(&output, pool));
157  SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2,
158                               options, pool));
159  return svn_diff_file_output_unified4(output, diff,
160                                       path1, path2,
161                                       label1, label2,
162                                       APR_LOCALE_CHARSET,
163                                       NULL,
164                                       options->show_c_function,
165                                       options->context_size,
166                                       cancel_func, cancel_baton,
167                                       pool);
168}
169
170
171/* Print on stdout just the conflict hunks of a diff among the 'base', 'their'
172 * and 'my' files of CONFLICT. */
173static svn_error_t *
174show_conflicts(svn_client_conflict_t *conflict,
175               svn_cancel_func_t cancel_func,
176               void *cancel_baton,
177               apr_pool_t *pool)
178{
179  svn_diff_t *diff;
180  svn_stream_t *output;
181  svn_diff_file_options_t *options;
182  const char *base_abspath;
183  const char *my_abspath;
184  const char *their_abspath;
185
186  SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
187                                                &base_abspath, &their_abspath,
188                                                conflict, pool, pool));
189  options = svn_diff_file_options_create(pool);
190  options->ignore_eol_style = TRUE;
191  SVN_ERR(svn_stream_for_stdout(&output, pool));
192  SVN_ERR(svn_diff_file_diff3_2(&diff, base_abspath, my_abspath, their_abspath,
193                                options, pool));
194  /* ### Consider putting the markers/labels from
195     ### svn_wc__merge_internal in the conflict description. */
196  return svn_diff_file_output_merge3(
197           output, diff, base_abspath, my_abspath, their_abspath,
198           _("||||||| ORIGINAL"),
199           _("<<<<<<< MINE (select with 'mc')"),
200           _(">>>>>>> THEIRS (select with 'tc')"),
201           "=======",
202           svn_diff_conflict_display_only_conflicts,
203           cancel_func,
204           cancel_baton,
205           pool);
206}
207
208/* Perform a 3-way merge of the conflicting values of a property,
209 * and write the result to the OUTPUT stream.
210 *
211 * If MERGED_PROPVAL is non-NULL, use it as 'my' version instead of
212 * MY_ABSPATH.
213 *
214 * Assume the values are printable UTF-8 text.
215 */
216static svn_error_t *
217merge_prop_conflict(svn_stream_t *output,
218                    const svn_string_t *base_propval,
219                    const svn_string_t *my_propval,
220                    const svn_string_t *their_propval,
221                    const svn_string_t *merged_propval,
222                    svn_cancel_func_t cancel_func,
223                    void *cancel_baton,
224                    apr_pool_t *pool)
225{
226  svn_diff_file_options_t *options = svn_diff_file_options_create(pool);
227  svn_diff_t *diff;
228
229  /* If any of the property values is missing, use an empty value instead
230   * for the purpose of showing a diff. */
231  if (base_propval == NULL)
232    base_propval = svn_string_create_empty(pool);
233  if (my_propval == NULL)
234    my_propval = svn_string_create_empty(pool);
235  if (their_propval == NULL)
236    their_propval = svn_string_create_empty(pool);
237
238  options->ignore_eol_style = TRUE;
239  SVN_ERR(svn_diff_mem_string_diff3(&diff, base_propval,
240                                    merged_propval ?
241                                      merged_propval : my_propval,
242                                    their_propval, options, pool));
243  SVN_ERR(svn_diff_mem_string_output_merge3(
244            output, diff, base_propval,
245            merged_propval ? merged_propval : my_propval, their_propval,
246            _("||||||| ORIGINAL"),
247            _("<<<<<<< MINE"),
248            _(">>>>>>> THEIRS"),
249            "=======",
250            svn_diff_conflict_display_modified_original_latest,
251            cancel_func,
252            cancel_baton,
253            pool));
254
255  return SVN_NO_ERROR;
256}
257
258/* Display the conflicting values of a property as a 3-way diff.
259 *
260 * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of
261 * DESC->MY_ABSPATH.
262 *
263 * Assume the values are printable UTF-8 text.
264 */
265static svn_error_t *
266show_prop_conflict(const svn_string_t *base_propval,
267                   const svn_string_t *my_propval,
268                   const svn_string_t *their_propval,
269                   const svn_string_t *merged_propval,
270                   svn_cancel_func_t cancel_func,
271                   void *cancel_baton,
272                   apr_pool_t *pool)
273{
274  svn_stream_t *output;
275
276  SVN_ERR(svn_stream_for_stdout(&output, pool));
277  SVN_ERR(merge_prop_conflict(output, base_propval, my_propval, their_propval,
278                              merged_propval, cancel_func, cancel_baton, pool));
279
280  return SVN_NO_ERROR;
281}
282
283/* Run an external editor, passing it the MERGED_ABSPATH, or, if the
284 * 'merged' file is null, return an error. The tool to use is determined by
285 * B->editor_cmd, B->config and environment variables; see
286 * svn_cl__edit_file_externally() for details.
287 *
288 * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
289 * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
290 * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
291 * return that error. */
292static svn_error_t *
293open_editor(svn_boolean_t *performed_edit,
294            const char *merged_abspath,
295            const char *editor_cmd,
296            apr_hash_t *config,
297            apr_pool_t *pool)
298{
299  svn_error_t *err;
300
301  if (merged_abspath)
302    {
303      err = svn_cmdline__edit_file_externally(merged_abspath, editor_cmd,
304                                              config, pool);
305      if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR ||
306                  err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
307        {
308          char buf[1024];
309          const char *message;
310
311          message = svn_err_best_message(err, buf, sizeof(buf));
312          SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", message));
313          svn_error_clear(err);
314        }
315      else if (err)
316        return svn_error_trace(err);
317      else
318        *performed_edit = TRUE;
319    }
320  else
321    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
322                                _("Invalid option; there's no "
323                                  "merged version to edit.\n\n")));
324
325  return SVN_NO_ERROR;
326}
327
328/* Run an external editor on the merged property value with conflict markers.
329 * Return the edited result in *MERGED_PROPVAL.
330 * If the edit is aborted, set *MERGED_ABSPATH and *MERGED_PROPVAL to NULL.
331 * The tool to use is determined by B->editor_cmd, B->config and
332 * environment variables; see svn_cl__edit_file_externally() for details. */
333static svn_error_t *
334edit_prop_conflict(const svn_string_t **merged_propval,
335                   const svn_string_t *base_propval,
336                   const svn_string_t *my_propval,
337                   const svn_string_t *their_propval,
338                   const char *editor_cmd,
339                   apr_hash_t *config,
340                   svn_cmdline_prompt_baton_t *pb,
341                   apr_pool_t *result_pool,
342                   apr_pool_t *scratch_pool)
343{
344  const char *file_path;
345  svn_boolean_t performed_edit = FALSE;
346  svn_stream_t *merged_prop;
347
348  SVN_ERR(svn_stream_open_unique(&merged_prop, &file_path, NULL,
349                                 svn_io_file_del_on_pool_cleanup,
350                                 scratch_pool, scratch_pool));
351  SVN_ERR(merge_prop_conflict(merged_prop, base_propval, my_propval,
352                              their_propval, NULL,
353                              pb->cancel_func,
354                              pb->cancel_baton,
355                              scratch_pool));
356  SVN_ERR(svn_stream_close(merged_prop));
357  SVN_ERR(open_editor(&performed_edit, file_path, editor_cmd,
358                      config, scratch_pool));
359  if (performed_edit && merged_propval)
360    {
361      svn_stringbuf_t *buf;
362
363      SVN_ERR(svn_stringbuf_from_file2(&buf, file_path, scratch_pool));
364      *merged_propval = svn_string_create_from_buf(buf, result_pool);
365    }
366
367  return SVN_NO_ERROR;
368}
369
370/* Maximum line length for the prompt string. */
371#define MAX_PROMPT_WIDTH 70
372
373/* Description of a resolver option.
374 * Resolver options are used to build the resolver's conflict prompt.
375 * The user types a code to select the corresponding conflict resolution option.
376 * Some resolver options have a corresponding --accept argument. */
377typedef struct resolver_option_t
378{
379  const char *code;        /* one or two characters */
380  svn_client_conflict_option_id_t choice;
381                           /* or ..._undefined if not from libsvn_client */
382  const char *accept_arg;  /* --accept option argument (NOT localized) */
383} resolver_option_t;
384
385typedef struct client_option_t
386{
387  const char *code;        /* one or two characters */
388  const char *label;       /* label in prompt (localized) */
389  const char *long_desc;   /* longer description (localized) */
390  svn_client_conflict_option_id_t choice;
391                           /* or ..._undefined if not from libsvn_client */
392  const char *accept_arg;  /* --accept option argument (NOT localized) */
393  svn_boolean_t is_recommended; /* if TRUE, try this option before prompting */
394} client_option_t;
395
396/* Resolver options for conflict options offered by libsvn_client.  */
397static const resolver_option_t builtin_resolver_options[] =
398{
399  { "r",  svn_client_conflict_option_merged_text,
400          SVN_CL__ACCEPT_WORKING },
401  { "mc", svn_client_conflict_option_working_text_where_conflicted,
402          SVN_CL__ACCEPT_MINE_CONFLICT },
403  { "tc", svn_client_conflict_option_incoming_text_where_conflicted,
404          SVN_CL__ACCEPT_THEIRS_CONFLICT },
405  { "mf", svn_client_conflict_option_working_text,
406          SVN_CL__ACCEPT_MINE_FULL},
407  { "tf", svn_client_conflict_option_incoming_text,
408          SVN_CL__ACCEPT_THEIRS_FULL },
409  { "p",  svn_client_conflict_option_postpone,
410          SVN_CL__ACCEPT_POSTPONE },
411
412  /* This option resolves a tree conflict to the current working copy state. */
413  { "r", svn_client_conflict_option_accept_current_wc_state,
414         SVN_CL__ACCEPT_WORKING },
415
416  /* These options use the same code since they only occur in
417   * distinct conflict scenarios. */
418  { "u", svn_client_conflict_option_update_move_destination },
419  { "u", svn_client_conflict_option_update_any_moved_away_children },
420
421  /* Options for incoming add vs local add. */
422  { "i", svn_client_conflict_option_incoming_add_ignore },
423
424  /* Options for incoming file add vs local file add upon merge. */
425  { "m", svn_client_conflict_option_incoming_added_file_text_merge },
426  { "M", svn_client_conflict_option_incoming_added_file_replace_and_merge },
427
428  /* Options for incoming dir add vs local dir add upon merge. */
429  { "m", svn_client_conflict_option_incoming_added_dir_merge },
430  { "R", svn_client_conflict_option_incoming_added_dir_replace },
431  { "M", svn_client_conflict_option_incoming_added_dir_replace_and_merge },
432
433  /* Options for incoming delete vs any. */
434  { "i", svn_client_conflict_option_incoming_delete_ignore },
435  { "a", svn_client_conflict_option_incoming_delete_accept },
436
437  /* Options for incoming move vs local edit. */
438  { "m", svn_client_conflict_option_incoming_move_file_text_merge },
439  { "m", svn_client_conflict_option_incoming_move_dir_merge },
440
441  /* Options for local move vs incoming edit. */
442  { "m", svn_client_conflict_option_local_move_file_text_merge },
443  { "m", svn_client_conflict_option_local_move_dir_merge },
444
445  /* Options for local missing vs incoming edit. */
446  { "m", svn_client_conflict_option_sibling_move_file_text_merge },
447  { "m", svn_client_conflict_option_sibling_move_dir_merge },
448
449  /* Options for incoming move vs local move. */
450  { "m", svn_client_conflict_option_both_moved_file_merge },
451  { "M", svn_client_conflict_option_both_moved_file_move_merge },
452  { "m", svn_client_conflict_option_both_moved_dir_merge },
453  { "M", svn_client_conflict_option_both_moved_dir_move_merge },
454
455  { NULL }
456};
457
458/* Extra resolver options offered by 'svn' for any conflict. */
459static const client_option_t extra_resolver_options[] =
460{
461  /* Translators: keep long_desc below 70 characters (wrap with a left
462     margin of 9 spaces if needed) */
463  { "q",  N_("Quit resolution"),  N_("postpone all remaining conflicts"),
464                                  svn_client_conflict_option_postpone },
465  { NULL }
466};
467
468
469/* Additional resolver options offered by 'svn' for a text conflict. */
470static const client_option_t extra_resolver_options_text[] =
471{
472  /* Translators: keep long_desc below 70 characters (wrap with a left
473     margin of 9 spaces if needed) */
474  { "e",  N_("Edit file"),        N_("change merged file in an editor"),
475                                  svn_client_conflict_option_undefined,
476                                  SVN_CL__ACCEPT_EDIT },
477  { "df", N_("Show diff"),        N_("show all changes made to merged file"),
478                                  svn_client_conflict_option_undefined},
479  { "dc", N_("Display conflict"), N_("show all conflicts "
480                                     "(ignoring merged version)"),
481                                  svn_client_conflict_option_undefined },
482  { "m",  N_("Merge"),            N_("use merge tool to resolve conflict"),
483                                  svn_client_conflict_option_undefined },
484  { "l",  N_("Launch tool"),      N_("launch external merge tool to resolve "
485                                     "conflict"),
486                                  svn_client_conflict_option_undefined,
487                                  SVN_CL__ACCEPT_LAUNCH },
488  { "i",  N_("Internal merge tool"), N_("use built-in merge tool to "
489                                     "resolve conflict"),
490                                  svn_client_conflict_option_undefined },
491  { "s",  N_("Show all options"), N_("show this list (also 'h', '?')"),
492                                  svn_client_conflict_option_undefined },
493  { NULL }
494};
495
496/* Additional resolver options offered by 'svn' for a property conflict. */
497static const client_option_t extra_resolver_options_prop[] =
498{
499  /* Translators: keep long_desc below 70 characters (wrap with a left
500     margin of 9 spaces if needed) */
501  { "dc", N_("Display conflict"), N_("show conflicts in this property"),
502                                  svn_client_conflict_option_undefined },
503  { "e",  N_("Edit property"),    N_("change merged property value in an "
504                                     "editor"),
505                                  svn_client_conflict_option_undefined,
506                                  SVN_CL__ACCEPT_EDIT },
507  { "h",  N_("Help"),             N_("show this help (also '?')"),
508                                  svn_client_conflict_option_undefined },
509  { NULL }
510};
511
512/* Additional resolver options offered by 'svn' for a tree conflict. */
513static const client_option_t extra_resolver_options_tree[] =
514{
515  /* Translators: keep long_desc below 70 characters (wrap with a left
516     margin of 9 spaces if needed) */
517  { "d",  N_("Set repository move destination path"),
518          N_("pick repository move target from list of possible targets"),
519                                  svn_client_conflict_option_undefined },
520
521  { "w",  N_("Set working copy move destination path"),
522          N_("pick working copy move target from list of possible targets"),
523                                  svn_client_conflict_option_undefined },
524
525  { "h",  N_("Help"),             N_("show this help (also '?')"),
526                                  svn_client_conflict_option_undefined },
527
528  { NULL }
529};
530
531
532/* Return a pointer to the option description in OPTIONS matching the
533 * one- or two-character OPTION_CODE.  Return NULL if not found. */
534static const client_option_t *
535find_option(const apr_array_header_t *options,
536            const char *option_code)
537{
538  int i;
539
540  for (i = 0; i < options->nelts; i++)
541    {
542      const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *);
543
544      /* Ignore code "" (blank lines) which is not a valid answer. */
545      if (opt->code[0] && strcmp(opt->code, option_code) == 0)
546        return opt;
547    }
548  return NULL;
549}
550
551/* Find the first recommended option in OPTIONS. */
552static const client_option_t *
553find_recommended_option(const apr_array_header_t *options)
554{
555  int i;
556
557  for (i = 0; i < options->nelts; i++)
558    {
559      const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *);
560
561      /* Ignore code "" (blank lines) which is not a valid answer. */
562      if (opt->code[0] && opt->is_recommended)
563        return opt;
564    }
565  return NULL;
566}
567
568/* Return a pointer to the client_option_t in OPTIONS matching the ID of
569 * conflict option BUILTIN_OPTION. @a out will be set to NULL if the
570 * option was not found. */
571static svn_error_t *
572find_option_by_builtin(client_option_t **out,
573                       svn_client_conflict_t *conflict,
574                       const resolver_option_t *options,
575                       svn_client_conflict_option_t *builtin_option,
576                       apr_pool_t *result_pool,
577                       apr_pool_t *scratch_pool)
578{
579  const resolver_option_t *opt;
580  svn_client_conflict_option_id_t id;
581  svn_client_conflict_option_id_t recommended_id;
582
583  id = svn_client_conflict_option_get_id(builtin_option);
584  recommended_id = svn_client_conflict_get_recommended_option_id(conflict);
585
586  for (opt = options; opt->code; opt++)
587    {
588      if (opt->choice == id)
589        {
590          client_option_t *client_opt;
591
592          client_opt = apr_pcalloc(result_pool, sizeof(*client_opt));
593          client_opt->choice = id;
594          client_opt->code = opt->code;
595          client_opt->label = svn_client_conflict_option_get_label(
596              builtin_option,
597              result_pool);
598          client_opt->long_desc = svn_client_conflict_option_get_description(
599                                    builtin_option,
600                                    result_pool);
601          client_opt->accept_arg = opt->accept_arg;
602          client_opt->is_recommended =
603            (recommended_id != svn_client_conflict_option_unspecified &&
604             id == recommended_id);
605
606          *out = client_opt;
607
608          return SVN_NO_ERROR;
609        }
610    }
611
612  *out = NULL;
613
614  return SVN_NO_ERROR;
615}
616
617/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
618 * non-null, select only the options whose codes are mentioned in it. */
619static const char *
620prompt_string(const apr_array_header_t *options,
621              const char *const *option_codes,
622              apr_pool_t *pool)
623{
624  const char *result = _("Select:");
625  int left_margin = svn_utf_cstring_utf8_width(result);
626  const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
627  int this_line_len = left_margin;
628  svn_boolean_t first = TRUE;
629  int i = 0;
630
631  while (1)
632    {
633      const client_option_t *opt;
634      const char *s;
635      int slen;
636
637      if (option_codes)
638        {
639          if (! *option_codes)
640            break;
641          opt = find_option(options, *option_codes++);
642          if (opt == NULL)
643            continue;
644        }
645      else
646        {
647          if (i >= options->nelts)
648            break;
649          opt = APR_ARRAY_IDX(options, i, client_option_t *);
650          i++;
651        }
652
653      if (! first)
654        result = apr_pstrcat(pool, result, ",", SVN_VA_NULL);
655      s = apr_psprintf(pool, " (%s) %s", opt->code,
656                       opt->label ? opt->label : opt->long_desc);
657      slen = svn_utf_cstring_utf8_width(s);
658      /* Break the line if adding the next option would make it too long */
659      if (this_line_len + slen > MAX_PROMPT_WIDTH)
660        {
661          result = apr_pstrcat(pool, result, line_sep, SVN_VA_NULL);
662          this_line_len = left_margin;
663        }
664      result = apr_pstrcat(pool, result, s, SVN_VA_NULL);
665      this_line_len += slen;
666      first = FALSE;
667    }
668  return apr_pstrcat(pool, result, ": ", SVN_VA_NULL);
669}
670
671/* Return a help string listing the OPTIONS. */
672static svn_error_t *
673help_string(const char **result,
674            const apr_array_header_t *options,
675            apr_pool_t *pool)
676{
677  apr_pool_t *iterpool;
678  int i;
679
680  *result = "";
681  iterpool = svn_pool_create(pool);
682  for (i = 0; i < options->nelts; i++)
683    {
684      const client_option_t *opt;
685      svn_pool_clear(iterpool);
686
687      opt = APR_ARRAY_IDX(options, i,
688                          client_option_t *);
689
690      /* Append a line describing OPT, or a blank line if its code is "". */
691      if (opt->code[0])
692        {
693          const char *s = apr_psprintf(pool, "  (%s)", opt->code);
694
695          if (opt->accept_arg)
696            *result = apr_psprintf(pool, "%s%-6s - %s  [%s]\n",
697                                   *result, s, opt->long_desc,
698                                   opt->accept_arg);
699          else
700            *result = apr_psprintf(pool, "%s%-6s - %s\n", *result, s,
701                                   opt->long_desc);
702        }
703      else
704        {
705          *result = apr_pstrcat(pool, *result, "\n", SVN_VA_NULL);
706        }
707    }
708  svn_pool_destroy(iterpool);
709  *result = apr_pstrcat(pool, *result,
710                       _("Words in square brackets are the corresponding "
711                         "--accept option arguments.\n"),
712                       SVN_VA_NULL);
713  return SVN_NO_ERROR;
714}
715
716/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
717 * in OPTIONS_TO_SHOW if that is non-null.  Set *OPT to point to the chosen
718 * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
719 * NULL if the answer was not one of them.
720 *
721 * If the answer is the (globally recognized) 'help' option, then display
722 * CONFLICT_DESCRIPTION (if not NULL) and help (on stderr) and return with
723 * *OPT == NULL.
724 */
725static svn_error_t *
726prompt_user(const client_option_t **opt,
727            const apr_array_header_t *conflict_options,
728            const char *const *options_to_show,
729            const char *conflict_description,
730            void *prompt_baton,
731            apr_pool_t *scratch_pool)
732{
733  const char *prompt
734    = prompt_string(conflict_options, options_to_show, scratch_pool);
735  const char *answer;
736
737  SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
738  if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
739    {
740      const char *helpstr;
741
742      if (conflict_description)
743        SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
744                                    conflict_description));
745      SVN_ERR(help_string(&helpstr, conflict_options, scratch_pool));
746      SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", helpstr));
747      *opt = NULL;
748    }
749  else
750    {
751      *opt = find_option(conflict_options, answer);
752      if (! *opt)
753        {
754          SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
755                                      _("Unrecognized option.\n\n")));
756        }
757    }
758  return SVN_NO_ERROR;
759}
760
761/* Set *OPTIONS to an array of resolution options for CONFLICT. */
762static svn_error_t *
763build_text_conflict_options(apr_array_header_t **options,
764                            svn_client_conflict_t *conflict,
765                            svn_client_ctx_t *ctx,
766                            svn_boolean_t is_binary,
767                            apr_pool_t *result_pool,
768                            apr_pool_t *scratch_pool)
769{
770  const client_option_t *o;
771  apr_array_header_t *builtin_options;
772  int nopt;
773  int i;
774  apr_pool_t *iterpool;
775
776  SVN_ERR(svn_client_conflict_text_get_resolution_options(&builtin_options,
777                                                          conflict, ctx,
778                                                          scratch_pool,
779                                                          scratch_pool));
780  nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options);
781  if (!is_binary)
782    nopt += ARRAY_LEN(extra_resolver_options_text);
783  *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
784
785  iterpool = svn_pool_create(scratch_pool);
786  for (i = 0; i < builtin_options->nelts; i++)
787    {
788      client_option_t *opt;
789      svn_client_conflict_option_t *builtin_option;
790
791      svn_pool_clear(iterpool);
792      builtin_option = APR_ARRAY_IDX(builtin_options, i,
793                                     svn_client_conflict_option_t *);
794      SVN_ERR(find_option_by_builtin(&opt, conflict,
795                                     builtin_resolver_options,
796                                     builtin_option,
797                                     result_pool,
798                                     iterpool));
799      if (opt == NULL)
800        continue; /* ### unknown option -- assign a code dynamically? */
801
802      APR_ARRAY_PUSH(*options, client_option_t *) = opt;
803    }
804
805  for (o = extra_resolver_options; o->code; o++)
806    APR_ARRAY_PUSH(*options, const client_option_t *) = o;
807  if (!is_binary)
808    {
809      for (o = extra_resolver_options_text; o->code; o++)
810        APR_ARRAY_PUSH(*options, const client_option_t *) = o;
811    }
812
813  svn_pool_destroy(iterpool);
814
815  return SVN_NO_ERROR;
816}
817
818/* Mark CONFLICT as resolved to resolution option with ID OPTION_ID.
819 * If TEXT_CONFLICTED is true, resolve text conflicts described by CONFLICT.
820 * IF PROPNAME is not NULL, mark the conflict in the specified property as
821 * resolved. If PROPNAME is "", mark all property conflicts described by
822 * CONFLICT as resolved.
823 * If TREE_CONFLICTED is true, resolve tree conflicts described by CONFLICT.
824 * Adjust CONFLICT_STATS as necessary (PATH_PREFIX is needed for this step). */
825static svn_error_t *
826mark_conflict_resolved(svn_client_conflict_t *conflict,
827                       svn_client_conflict_option_id_t option_id,
828                       svn_boolean_t text_conflicted,
829                       const char *propname,
830                       svn_boolean_t tree_conflicted,
831                       const char *path_prefix,
832                       svn_cl__conflict_stats_t *conflict_stats,
833                       svn_client_ctx_t *ctx,
834                       apr_pool_t *scratch_pool)
835{
836  const char *local_relpath
837    = svn_cl__local_style_skip_ancestor(
838        path_prefix, svn_client_conflict_get_local_abspath(conflict),
839        scratch_pool);
840
841  if (text_conflicted)
842    {
843      SVN_ERR(svn_client_conflict_text_resolve_by_id(conflict, option_id,
844                                                     ctx, scratch_pool));
845      svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
846                                      svn_wc_conflict_kind_text);
847    }
848
849  if (propname)
850    {
851      SVN_ERR(svn_client_conflict_prop_resolve_by_id(conflict, propname,
852                                                     option_id, ctx,
853                                                     scratch_pool));
854      svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
855                                      svn_wc_conflict_kind_property);
856    }
857
858  if (tree_conflicted)
859    {
860      SVN_ERR(svn_client_conflict_tree_resolve_by_id(conflict, option_id,
861                                                     ctx, scratch_pool));
862      svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
863                                      svn_wc_conflict_kind_tree);
864    }
865
866  return SVN_NO_ERROR;
867}
868
869/* Ask the user what to do about the text conflict described by CONFLICT
870 * and either resolve the conflict accordingly or postpone resolution.
871 * SCRATCH_POOL is used for temporary allocations. */
872static svn_error_t *
873handle_text_conflict(svn_boolean_t *resolved,
874                     svn_boolean_t *postponed,
875                     svn_boolean_t *quit,
876                     svn_boolean_t *printed_description,
877                     svn_client_conflict_t *conflict,
878                     const char *path_prefix,
879                     svn_cmdline_prompt_baton_t *pb,
880                     const char *editor_cmd,
881                     apr_hash_t *config,
882                     svn_cl__conflict_stats_t *conflict_stats,
883                     svn_client_ctx_t *ctx,
884                     apr_pool_t *scratch_pool)
885{
886  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
887  svn_boolean_t diff_allowed = FALSE;
888  /* Have they done something that might have affected the merged file? */
889  svn_boolean_t performed_edit = FALSE;
890  /* Have they done *something* (edit, look at diff, etc) to
891     give them a rational basis for choosing (r)esolved? */
892  svn_boolean_t knows_something = FALSE;
893  const char *local_relpath;
894  const char *local_abspath = svn_client_conflict_get_local_abspath(conflict);
895  const char *mime_type = svn_client_conflict_text_get_mime_type(conflict);
896  svn_boolean_t is_binary = mime_type ? svn_mime_type_is_binary(mime_type)
897                                      : FALSE;
898  const char *base_abspath;
899  const char *my_abspath;
900  const char *their_abspath;
901  const char *merged_abspath = svn_client_conflict_get_local_abspath(conflict);
902  apr_array_header_t *text_conflict_options;
903  svn_client_conflict_option_id_t option_id;
904
905  option_id = svn_client_conflict_option_unspecified;
906
907  SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
908                                                &base_abspath, &their_abspath,
909                                                conflict, scratch_pool,
910                                                scratch_pool));
911
912  local_relpath = svn_cl__local_style_skip_ancestor(path_prefix,
913                                                    local_abspath,
914                                                    scratch_pool);
915
916  if (!*printed_description)
917    {
918      if (is_binary)
919        SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
920                                    _("Merge conflict discovered in binary "
921                                      "file '%s'.\n"),
922                                    local_relpath));
923      else
924        SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
925                                    _("Merge conflict discovered in file '%s'.\n"),
926                                    local_relpath));
927      *printed_description = TRUE;
928    }
929
930  /* ### TODO This whole feature availability check is grossly outdated.
931     DIFF_ALLOWED needs either to be redefined or to go away.
932   */
933
934  /* Diffing can happen between base and merged, to show conflict
935     markers to the user (this is the typical 3-way merge
936     scenario), or if no base is available, we can show a diff
937     between mine and theirs. */
938  if (!is_binary &&
939      ((merged_abspath && base_abspath)
940      || (!base_abspath && my_abspath && their_abspath)))
941    diff_allowed = TRUE;
942
943  SVN_ERR(build_text_conflict_options(&text_conflict_options, conflict, ctx,
944                                      is_binary, scratch_pool, scratch_pool));
945  while (TRUE)
946    {
947      const char *suggested_options[9]; /* filled statically below */
948      const char **next_option = suggested_options;
949      const client_option_t *opt;
950
951      svn_pool_clear(iterpool);
952
953      *next_option++ = "p";
954      if (diff_allowed)
955        {
956          /* We need one more path for this feature. */
957          if (my_abspath)
958            *next_option++ = "df";
959
960          *next_option++ = "e";
961
962          /* We need one more path for this feature. */
963          if (my_abspath)
964            *next_option++ = "m";
965
966          if (knows_something)
967            *next_option++ = "r";
968        }
969      else
970        {
971          if (knows_something || is_binary)
972            *next_option++ = "r";
973
974          /* The 'mine-full' option selects the ".mine" file for texts or
975           * the current working directory file for binary files. */
976          if (my_abspath || is_binary)
977            *next_option++ = "mf";
978
979          *next_option++ = "tf";
980        }
981      *next_option++ = "s";
982      *next_option++ = NULL;
983
984      SVN_ERR(prompt_user(&opt, text_conflict_options, suggested_options,
985                          NULL, pb, iterpool));
986      if (! opt)
987        continue;
988
989      if (strcmp(opt->code, "q") == 0)
990        {
991          option_id = opt->choice;
992          *quit = TRUE;
993          break;
994        }
995      else if (strcmp(opt->code, "s") == 0)
996        {
997          const char *helpstr;
998
999          SVN_ERR(help_string(&helpstr, text_conflict_options, iterpool));
1000          SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
1001                                      helpstr));
1002        }
1003      else if (strcmp(opt->code, "dc") == 0)
1004        {
1005          if (is_binary)
1006            {
1007              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1008                                          _("Invalid option; cannot "
1009                                            "display conflicts for a "
1010                                            "binary file.\n\n")));
1011              continue;
1012            }
1013          else if (! (my_abspath && base_abspath && their_abspath))
1014            {
1015              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1016                                          _("Invalid option; original "
1017                                            "files not available.\n\n")));
1018              continue;
1019            }
1020          SVN_ERR(show_conflicts(conflict,
1021                                 pb->cancel_func,
1022                                 pb->cancel_baton,
1023                                 iterpool));
1024          knows_something = TRUE;
1025        }
1026      else if (strcmp(opt->code, "df") == 0)
1027        {
1028          /* Re-check preconditions. */
1029          if (! diff_allowed || ! my_abspath)
1030            {
1031              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1032                             _("Invalid option; there's no "
1033                                "merged version to diff.\n\n")));
1034              continue;
1035            }
1036
1037          SVN_ERR(show_diff(conflict, merged_abspath, path_prefix,
1038                            pb->cancel_func, pb->cancel_baton,
1039                            iterpool));
1040          knows_something = TRUE;
1041        }
1042      else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
1043        {
1044          SVN_ERR(open_editor(&performed_edit, merged_abspath, editor_cmd,
1045                              config, iterpool));
1046          if (performed_edit)
1047            knows_something = TRUE;
1048        }
1049      else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
1050               strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
1051        {
1052          svn_error_t *err;
1053
1054          /* Re-check preconditions. */
1055          if (! my_abspath)
1056            {
1057              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1058                             _("Invalid option; there's no "
1059                                "base path to merge.\n\n")));
1060              continue;
1061            }
1062
1063          err = svn_cl__merge_file_externally(base_abspath,
1064                                              their_abspath,
1065                                              my_abspath,
1066                                              merged_abspath,
1067                                              local_abspath, config,
1068                                              NULL, iterpool);
1069          if (err)
1070            {
1071              if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
1072                {
1073                  svn_boolean_t remains_in_conflict = TRUE;
1074
1075                  /* Try the internal merge tool. */
1076                  svn_error_clear(err);
1077                  SVN_ERR(svn_cl__merge_file(&remains_in_conflict,
1078                                             base_abspath,
1079                                             their_abspath,
1080                                             my_abspath,
1081                                             merged_abspath,
1082                                             local_abspath,
1083                                             path_prefix,
1084                                             editor_cmd,
1085                                             config,
1086                                             pb->cancel_func,
1087                                             pb->cancel_baton,
1088                                             iterpool));
1089                  knows_something = !remains_in_conflict;
1090                }
1091              else if (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
1092                {
1093                  char buf[1024];
1094                  const char *message;
1095
1096                  message = svn_err_best_message(err, buf, sizeof(buf));
1097                  SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1098                                              "%s\n", message));
1099                  svn_error_clear(err);
1100                  continue;
1101                }
1102              else
1103                return svn_error_trace(err);
1104            }
1105          else
1106            {
1107              /* The external merge tool's exit code was either 0 or 1.
1108               * The tool may leave the file conflicted by exiting with
1109               * exit code 1, and we allow the user to mark the conflict
1110               * resolved in this case. */
1111              performed_edit = TRUE;
1112              knows_something = TRUE;
1113            }
1114        }
1115      else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
1116        {
1117          /* ### This check should be earlier as it's nasty to offer an option
1118           *     and then when the user chooses it say 'Invalid option'. */
1119          /* ### 'merged_abspath' shouldn't be necessary *before* we launch the
1120           *     resolver: it should be the *result* of doing so. */
1121          if (base_abspath && their_abspath && my_abspath && merged_abspath)
1122            {
1123              svn_error_t *err;
1124              char buf[1024];
1125              const char *message;
1126
1127              err = svn_cl__merge_file_externally(base_abspath,
1128                                                  their_abspath,
1129                                                  my_abspath,
1130                                                  merged_abspath,
1131                                                  local_abspath,
1132                                                  config, NULL, iterpool);
1133              if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL ||
1134                          err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
1135                {
1136                  message = svn_err_best_message(err, buf, sizeof(buf));
1137                  SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n",
1138                                              message));
1139                  svn_error_clear(err);
1140                }
1141              else if (err)
1142                return svn_error_trace(err);
1143              else
1144                performed_edit = TRUE;
1145
1146              if (performed_edit)
1147                knows_something = TRUE;
1148            }
1149          else
1150            SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1151                                        _("Invalid option.\n\n")));
1152        }
1153      else if (strcmp(opt->code, "i") == 0)
1154        {
1155          svn_boolean_t remains_in_conflict = TRUE;
1156
1157          SVN_ERR(svn_cl__merge_file(&remains_in_conflict,
1158                                     base_abspath,
1159                                     their_abspath,
1160                                     my_abspath,
1161                                     merged_abspath,
1162                                     local_abspath,
1163                                     path_prefix,
1164                                     editor_cmd,
1165                                     config,
1166                                     pb->cancel_func,
1167                                     pb->cancel_baton,
1168                                     iterpool));
1169
1170          if (!remains_in_conflict)
1171            knows_something = TRUE;
1172        }
1173      else if (opt->choice != svn_client_conflict_option_undefined)
1174        {
1175          if ((opt->choice == svn_client_conflict_option_working_text_where_conflicted
1176               || opt->choice == svn_client_conflict_option_incoming_text_where_conflicted)
1177              && is_binary)
1178            {
1179              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1180                                          _("Invalid option; cannot choose "
1181                                            "based on conflicts in a "
1182                                            "binary file.\n\n")));
1183              continue;
1184            }
1185
1186          /* We only allow the user accept the merged version of
1187             the file if they've edited it, or at least looked at
1188             the diff. */
1189          if (opt->choice == svn_client_conflict_option_merged_text
1190              && ! knows_something && diff_allowed)
1191            {
1192              SVN_ERR(svn_cmdline_fprintf(
1193                        stderr, iterpool,
1194                        _("Invalid option; use diff/edit/merge/launch "
1195                          "before choosing 'mark resolved'.\n\n")));
1196              continue;
1197            }
1198
1199          option_id = opt->choice;
1200          break;
1201        }
1202    }
1203  svn_pool_destroy(iterpool);
1204
1205  if (option_id != svn_client_conflict_option_unspecified &&
1206      option_id != svn_client_conflict_option_postpone)
1207    {
1208      SVN_ERR(mark_conflict_resolved(conflict, option_id,
1209                                     TRUE, NULL, FALSE,
1210                                     path_prefix, conflict_stats,
1211                                     ctx, scratch_pool));
1212      *resolved = TRUE;
1213    }
1214  else
1215    {
1216      *resolved = FALSE;
1217      *postponed = (option_id == svn_client_conflict_option_postpone);
1218    }
1219
1220  return SVN_NO_ERROR;
1221}
1222
1223/* Set *OPTIONS to an array of resolution options for CONFLICT. */
1224static svn_error_t *
1225build_prop_conflict_options(apr_array_header_t **options,
1226                            svn_client_conflict_t *conflict,
1227                            svn_client_ctx_t *ctx,
1228                            apr_pool_t *result_pool,
1229                            apr_pool_t *scratch_pool)
1230{
1231  const client_option_t *o;
1232  apr_array_header_t *builtin_options;
1233  int nopt;
1234  int i;
1235  apr_pool_t *iterpool;
1236
1237  SVN_ERR(svn_client_conflict_prop_get_resolution_options(&builtin_options,
1238                                                          conflict, ctx,
1239                                                          scratch_pool,
1240                                                          scratch_pool));
1241  nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options) +
1242           ARRAY_LEN(extra_resolver_options_prop);
1243  *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
1244
1245  iterpool = svn_pool_create(scratch_pool);
1246  for (i = 0; i < builtin_options->nelts; i++)
1247    {
1248      client_option_t *opt;
1249      svn_client_conflict_option_t *builtin_option;
1250
1251      svn_pool_clear(iterpool);
1252      builtin_option = APR_ARRAY_IDX(builtin_options, i,
1253                                     svn_client_conflict_option_t *);
1254      SVN_ERR(find_option_by_builtin(&opt, conflict,
1255                                     builtin_resolver_options,
1256                                     builtin_option,
1257                                     result_pool,
1258                                     iterpool));
1259      if (opt == NULL)
1260        continue; /* ### unknown option -- assign a code dynamically? */
1261
1262      APR_ARRAY_PUSH(*options, client_option_t *) = opt;
1263    }
1264
1265  svn_pool_destroy(iterpool);
1266
1267  for (o = extra_resolver_options; o->code; o++)
1268    APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1269  for (o = extra_resolver_options_prop; o->code; o++)
1270    APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1271
1272  return SVN_NO_ERROR;
1273}
1274
1275/* Ask the user what to do about the conflicted property PROPNAME described
1276 * by CONFLICT and return the corresponding resolution option in *OPTION.
1277 * SCRATCH_POOL is used for temporary allocations. */
1278static svn_error_t *
1279handle_one_prop_conflict(svn_client_conflict_option_t **option,
1280                         svn_boolean_t *quit,
1281                         const char *path_prefix,
1282                         svn_cmdline_prompt_baton_t *pb,
1283                         const char *editor_cmd,
1284                         apr_hash_t *config,
1285                         svn_client_conflict_t *conflict,
1286                         const char *propname,
1287                         svn_client_ctx_t *ctx,
1288                         apr_pool_t *result_pool,
1289                         apr_pool_t *scratch_pool)
1290{
1291  apr_pool_t *iterpool;
1292  const char *description;
1293  const svn_string_t *merged_propval = NULL;
1294  svn_boolean_t resolved_allowed = FALSE;
1295  const svn_string_t *base_propval;
1296  const svn_string_t *my_propval;
1297  const svn_string_t *their_propval;
1298  apr_array_header_t *resolution_options;
1299  apr_array_header_t *prop_conflict_options;
1300
1301  SVN_ERR(svn_client_conflict_prop_get_propvals(NULL, &my_propval,
1302                                                &base_propval, &their_propval,
1303                                                conflict, propname,
1304                                                scratch_pool));
1305
1306  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1307                              _("Conflict for property '%s' discovered"
1308                                " on '%s'.\n"),
1309                              propname,
1310                              svn_cl__local_style_skip_ancestor(
1311                                path_prefix,
1312                                svn_client_conflict_get_local_abspath(conflict),
1313                                scratch_pool)));
1314  SVN_ERR(svn_client_conflict_prop_get_description(&description, conflict,
1315                                                   scratch_pool, scratch_pool));
1316  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", description));
1317
1318  SVN_ERR(svn_client_conflict_prop_get_resolution_options(&resolution_options,
1319                                                          conflict, ctx,
1320                                                          result_pool,
1321                                                          scratch_pool));
1322  SVN_ERR(build_prop_conflict_options(&prop_conflict_options, conflict, ctx,
1323                                      scratch_pool, scratch_pool));
1324  iterpool = svn_pool_create(scratch_pool);
1325  while (TRUE)
1326    {
1327      const client_option_t *opt;
1328      const char *suggested_options[9]; /* filled statically below */
1329      const char **next_option = suggested_options;
1330
1331      *next_option++ = "p";
1332      *next_option++ = "mf";
1333      *next_option++ = "tf";
1334      *next_option++ = "dc";
1335      *next_option++ = "e";
1336      if (resolved_allowed)
1337        *next_option++ = "r";
1338      *next_option++ = "q";
1339      *next_option++ = "h";
1340      *next_option++ = NULL;
1341
1342      svn_pool_clear(iterpool);
1343
1344      SVN_ERR(prompt_user(&opt, prop_conflict_options, suggested_options,
1345                          NULL, pb, iterpool));
1346      if (! opt)
1347        continue;
1348
1349      if (strcmp(opt->code, "q") == 0)
1350        {
1351          *option = svn_client_conflict_option_find_by_id(resolution_options,
1352                                                          opt->choice);
1353          *quit = TRUE;
1354          break;
1355        }
1356      else if (strcmp(opt->code, "dc") == 0)
1357        {
1358          SVN_ERR(show_prop_conflict(base_propval, my_propval, their_propval,
1359                                     merged_propval,
1360                                     pb->cancel_func, pb->cancel_baton,
1361                                     scratch_pool));
1362        }
1363      else if (strcmp(opt->code, "e") == 0)
1364        {
1365          SVN_ERR(edit_prop_conflict(&merged_propval,
1366                                     base_propval, my_propval, their_propval,
1367                                     editor_cmd, config, pb,
1368                                     result_pool, scratch_pool));
1369          resolved_allowed = (merged_propval != NULL);
1370        }
1371      else if (strcmp(opt->code, "r") == 0)
1372        {
1373          if (! resolved_allowed)
1374            {
1375              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1376                             _("Invalid option; please edit the property "
1377                               "first.\n\n")));
1378              continue;
1379            }
1380
1381          *option = svn_client_conflict_option_find_by_id(
1382                      resolution_options,
1383                      svn_client_conflict_option_merged_text);
1384          svn_client_conflict_option_set_merged_propval(*option,
1385                                                        merged_propval);
1386          break;
1387        }
1388      else if (opt->choice != svn_client_conflict_option_undefined)
1389        {
1390          *option = svn_client_conflict_option_find_by_id(resolution_options,
1391                                                          opt->choice);
1392          break;
1393        }
1394    }
1395  svn_pool_destroy(iterpool);
1396
1397  return SVN_NO_ERROR;
1398}
1399
1400/* Ask the user what to do about the property conflicts described by CONFLICT
1401 * and either resolve them accordingly or postpone resolution.
1402 * SCRATCH_POOL is used for temporary allocations. */
1403static svn_error_t *
1404handle_prop_conflicts(svn_boolean_t *resolved,
1405                      svn_boolean_t *postponed,
1406                      svn_boolean_t *quit,
1407                      const svn_string_t **merged_value,
1408                      const char *path_prefix,
1409                      svn_cmdline_prompt_baton_t *pb,
1410                      const char *editor_cmd,
1411                      apr_hash_t *config,
1412                      svn_client_conflict_t *conflict,
1413                      svn_cl__conflict_stats_t *conflict_stats,
1414                      svn_client_ctx_t *ctx,
1415                      apr_pool_t *result_pool,
1416                      apr_pool_t *scratch_pool)
1417{
1418  apr_array_header_t *props_conflicted;
1419  apr_pool_t *iterpool;
1420  int i;
1421  int nresolved = 0;
1422
1423  SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
1424                                             conflict, scratch_pool,
1425                                             scratch_pool));
1426
1427  iterpool = svn_pool_create(scratch_pool);
1428  for (i = 0; i < props_conflicted->nelts; i++)
1429    {
1430      const char *propname = APR_ARRAY_IDX(props_conflicted, i, const char *);
1431      svn_client_conflict_option_t *option;
1432      svn_client_conflict_option_id_t option_id;
1433
1434      svn_pool_clear(iterpool);
1435
1436      SVN_ERR(handle_one_prop_conflict(&option, quit, path_prefix, pb,
1437                                       editor_cmd, config, conflict, propname,
1438                                       ctx,
1439                                       iterpool, iterpool));
1440      option_id = svn_client_conflict_option_get_id(option);
1441
1442      if (option_id != svn_client_conflict_option_unspecified &&
1443          option_id != svn_client_conflict_option_postpone)
1444        {
1445          const char *local_relpath =
1446            svn_cl__local_style_skip_ancestor(
1447              path_prefix, svn_client_conflict_get_local_abspath(conflict),
1448              iterpool);
1449
1450          SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option,
1451                                                   ctx, iterpool));
1452          svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
1453                                          svn_wc_conflict_kind_property);
1454          nresolved++;
1455          *postponed = FALSE;
1456        }
1457      else
1458        *postponed = (option_id == svn_client_conflict_option_postpone);
1459
1460      if (*quit)
1461        break;
1462    }
1463  svn_pool_destroy(iterpool);
1464
1465  /* Indicate success if no property conflicts remain. */
1466  *resolved = (nresolved == props_conflicted->nelts);
1467
1468  return SVN_NO_ERROR;
1469}
1470
1471/* Set *OPTIONS to an array of resolution options for CONFLICT. */
1472static svn_error_t *
1473build_tree_conflict_options(
1474  apr_array_header_t **options,
1475  apr_array_header_t **possible_moved_to_repos_relpaths,
1476  apr_array_header_t **possible_moved_to_abspaths,
1477  svn_boolean_t *all_options_are_dumb,
1478  svn_client_conflict_t *conflict,
1479  svn_client_ctx_t *ctx,
1480  apr_pool_t *result_pool,
1481  apr_pool_t *scratch_pool)
1482{
1483  const client_option_t *o;
1484  apr_array_header_t *builtin_options;
1485  int nopt;
1486  int i;
1487  int next_unknown_option_code = 1;
1488  apr_pool_t *iterpool;
1489
1490  if (all_options_are_dumb != NULL)
1491    *all_options_are_dumb = TRUE;
1492
1493  SVN_ERR(svn_client_conflict_tree_get_resolution_options(&builtin_options,
1494                                                          conflict, ctx,
1495                                                          scratch_pool,
1496                                                          scratch_pool));
1497  nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options_tree) +
1498           ARRAY_LEN(extra_resolver_options);
1499  *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
1500  *possible_moved_to_abspaths = NULL;
1501  *possible_moved_to_repos_relpaths = NULL;
1502
1503  iterpool = svn_pool_create(scratch_pool);
1504  for (i = 0; i < builtin_options->nelts; i++)
1505    {
1506      client_option_t *opt;
1507      svn_client_conflict_option_t *builtin_option;
1508      svn_client_conflict_option_id_t id;
1509
1510      svn_pool_clear(iterpool);
1511      builtin_option = APR_ARRAY_IDX(builtin_options, i,
1512                                     svn_client_conflict_option_t *);
1513      SVN_ERR(find_option_by_builtin(&opt, conflict,
1514                                     builtin_resolver_options,
1515                                     builtin_option,
1516                                     result_pool,
1517                                     iterpool));
1518      if (opt == NULL)
1519        {
1520          /* Unkown option. Assign a dynamic option code. */
1521          opt = apr_pcalloc(result_pool, sizeof(*opt));
1522          opt->code = apr_psprintf(result_pool, "%d", next_unknown_option_code);
1523          next_unknown_option_code++;
1524          opt->label = svn_client_conflict_option_get_label(builtin_option,
1525                                                            result_pool);
1526          opt->long_desc = svn_client_conflict_option_get_description(
1527                             builtin_option, result_pool);
1528          opt->choice = svn_client_conflict_option_get_id(builtin_option);
1529          opt->accept_arg = NULL;
1530        }
1531
1532      APR_ARRAY_PUSH(*options, client_option_t *) = opt;
1533
1534      id = svn_client_conflict_option_get_id(builtin_option);
1535
1536      /* Check if we got a "smart" tree conflict option. */
1537      if (all_options_are_dumb != NULL &&
1538          *all_options_are_dumb &&
1539          id != svn_client_conflict_option_postpone &&
1540          id != svn_client_conflict_option_accept_current_wc_state)
1541        *all_options_are_dumb = FALSE;
1542
1543      if (*possible_moved_to_repos_relpaths == NULL)
1544        SVN_ERR(
1545          svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(
1546            possible_moved_to_repos_relpaths, builtin_option,
1547            result_pool, iterpool));
1548
1549      if (*possible_moved_to_abspaths == NULL)
1550        SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2(
1551                  possible_moved_to_abspaths, builtin_option,
1552                  result_pool, iterpool));
1553    }
1554
1555  svn_pool_destroy(iterpool);
1556
1557  for (o = extra_resolver_options_tree; o->code; o++)
1558    {
1559      /* Add move target choice options only if there are multiple
1560       * move targets to choose from. */
1561      if (strcmp(o->code, "d") == 0 &&
1562          (*possible_moved_to_repos_relpaths == NULL ||
1563           (*possible_moved_to_repos_relpaths)->nelts <= 1))
1564        continue;
1565      if (strcmp(o->code, "w") == 0 &&
1566          (*possible_moved_to_abspaths == NULL ||
1567           (*possible_moved_to_abspaths)->nelts <= 1))
1568        continue;
1569
1570      APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1571    }
1572  for (o = extra_resolver_options; o->code; o++)
1573    APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1574
1575  return SVN_NO_ERROR;
1576}
1577
1578/* Make the user select a move target path for the moved-away VICTIM_ABSPATH. */
1579static svn_error_t *
1580prompt_move_target_path(int *preferred_move_target_idx,
1581                        apr_array_header_t *possible_moved_to_paths,
1582                        svn_boolean_t paths_are_local,
1583                        svn_cmdline_prompt_baton_t *pb,
1584                        const char *victim_abspath,
1585                        svn_client_ctx_t *ctx,
1586                        apr_pool_t *scratch_pool)
1587{
1588  const char *move_targets_prompt = "";
1589  const char *move_targets_list = "";
1590  const char *wcroot_abspath;
1591  const char *victim_relpath;
1592  int i;
1593  apr_int64_t idx;
1594  apr_pool_t *iterpool;
1595
1596  SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, victim_abspath,
1597                                 ctx, scratch_pool, scratch_pool));
1598  victim_relpath = svn_cl__local_style_skip_ancestor(wcroot_abspath,
1599                                                     victim_abspath,
1600                                                     scratch_pool),
1601  iterpool = svn_pool_create(scratch_pool);
1602
1603  /* Build the prompt. */
1604  for (i = 0; i < possible_moved_to_paths->nelts; i++)
1605    {
1606      svn_pool_clear(iterpool);
1607
1608      if (paths_are_local)
1609        {
1610          const char *moved_to_abspath;
1611          const char *moved_to_relpath;
1612
1613          moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_paths, i,
1614                                           const char *);
1615          moved_to_relpath = svn_cl__local_style_skip_ancestor(
1616                               wcroot_abspath, moved_to_abspath, iterpool),
1617          move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '%s'\n",
1618                                           move_targets_list, i + 1,
1619                                           moved_to_relpath);
1620        }
1621      else
1622        {
1623          const char *moved_to_repos_relpath;
1624
1625          moved_to_repos_relpath = APR_ARRAY_IDX(possible_moved_to_paths, i,
1626                                                 const char *);
1627          move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '^/%s'\n",
1628                                           move_targets_list, i + 1,
1629                                           moved_to_repos_relpath);
1630        }
1631    }
1632  if (paths_are_local)
1633    move_targets_prompt =
1634      apr_psprintf(scratch_pool,
1635                   _("Possible working copy destinations for moved-away '%s' "
1636                     "are:\n%s"
1637                     "Only one destination can be a move; the others are "
1638                     "copies.\n"
1639                     "Specify the correct move target path by number: "),
1640                   victim_relpath, move_targets_list);
1641  else
1642    move_targets_prompt =
1643      apr_psprintf(scratch_pool,
1644                   _("Possible repository destinations for moved-away '%s' "
1645                     "are:\n%s"
1646                     "Only one destination can be a move; the others are "
1647                     "copies.\n"
1648                     "Specify the correct move target path by number: "),
1649                   victim_relpath, move_targets_list);
1650
1651  /* Keep asking the user until we got a valid choice. */
1652  while (1)
1653    {
1654      const char *answer;
1655      svn_error_t *err;
1656
1657      svn_pool_clear(iterpool);
1658
1659      SVN_ERR(svn_cmdline_prompt_user2(&answer, move_targets_prompt,
1660                                       pb, iterpool));
1661      err = svn_cstring_strtoi64(&idx, answer, 1,
1662                                 possible_moved_to_paths->nelts, 10);
1663      if (err)
1664        {
1665          char buf[1024];
1666
1667          SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n",
1668                                      svn_err_best_message(err, buf, sizeof(buf))));
1669          svn_error_clear(err);
1670          continue;
1671        }
1672
1673      break;
1674    }
1675
1676  svn_pool_destroy(iterpool);
1677
1678  SVN_ERR_ASSERT((idx - 1) == (int)(idx - 1));
1679  *preferred_move_target_idx = (int)(idx - 1);
1680  return SVN_NO_ERROR;
1681}
1682
1683static svn_error_t *
1684find_conflict_option_with_repos_move_targets(
1685  svn_client_conflict_option_t **option_with_move_targets,
1686  apr_array_header_t *options,
1687  apr_pool_t *scratch_pool)
1688{
1689  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1690  int i;
1691  apr_array_header_t *possible_moved_to_repos_relpaths = NULL;
1692
1693  *option_with_move_targets = NULL;
1694
1695  for (i = 0; i < options->nelts; i++)
1696    {
1697      svn_client_conflict_option_t *option;
1698
1699      svn_pool_clear(iterpool);
1700      option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *);
1701      SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(
1702        &possible_moved_to_repos_relpaths, option, iterpool, iterpool));
1703      if (possible_moved_to_repos_relpaths)
1704        {
1705          *option_with_move_targets = option;
1706          break;
1707        }
1708    }
1709  svn_pool_destroy(iterpool);
1710
1711  return SVN_NO_ERROR;
1712}
1713
1714static svn_error_t *
1715find_conflict_option_with_working_copy_move_targets(
1716  svn_client_conflict_option_t **option_with_move_targets,
1717  apr_array_header_t *options,
1718  apr_pool_t *scratch_pool)
1719{
1720  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1721  int i;
1722  apr_array_header_t *possible_moved_to_abspaths = NULL;
1723
1724  *option_with_move_targets = NULL;
1725
1726  for (i = 0; i < options->nelts; i++)
1727    {
1728      svn_client_conflict_option_t *option;
1729
1730      svn_pool_clear(iterpool);
1731      option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *);
1732      SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2(
1733              &possible_moved_to_abspaths, option, scratch_pool,
1734              iterpool));
1735      if (possible_moved_to_abspaths)
1736        {
1737          *option_with_move_targets = option;
1738          break;
1739        }
1740    }
1741  svn_pool_destroy(iterpool);
1742
1743  return SVN_NO_ERROR;
1744}
1745
1746/* Ask the user what to do about the tree conflict described by CONFLICT
1747 * and either resolve the conflict accordingly or postpone resolution.
1748 * SCRATCH_POOL is used for temporary allocations. */
1749static svn_error_t *
1750handle_tree_conflict(svn_boolean_t *resolved,
1751                     svn_boolean_t *postponed,
1752                     svn_boolean_t *quit,
1753                     svn_boolean_t *printed_description,
1754                     svn_client_conflict_t *conflict,
1755                     const char *path_prefix,
1756                     svn_cmdline_prompt_baton_t *pb,
1757                     svn_cl__conflict_stats_t *conflict_stats,
1758                     svn_client_ctx_t *ctx,
1759                     apr_pool_t *scratch_pool)
1760{
1761  apr_pool_t *iterpool;
1762  apr_array_header_t *tree_conflict_options;
1763  svn_client_conflict_option_id_t option_id;
1764  const char *local_abspath;
1765  const char *conflict_description;
1766  const char *local_change_description;
1767  const char *incoming_change_description;
1768  apr_array_header_t *possible_moved_to_repos_relpaths;
1769  apr_array_header_t *possible_moved_to_abspaths;
1770  svn_boolean_t all_options_are_dumb;
1771  const struct client_option_t *recommended_option;
1772  svn_boolean_t repos_move_target_chosen = FALSE;
1773  svn_boolean_t wc_move_target_chosen = FALSE;
1774
1775  option_id = svn_client_conflict_option_unspecified;
1776  local_abspath = svn_client_conflict_get_local_abspath(conflict);
1777
1778  /* Always show the best possible conflict description and options. */
1779  SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, scratch_pool));
1780
1781  SVN_ERR(svn_client_conflict_tree_get_description(
1782           &incoming_change_description, &local_change_description,
1783           conflict, ctx, scratch_pool, scratch_pool));
1784  conflict_description = apr_psprintf(scratch_pool, "%s\n%s",
1785                                      incoming_change_description,
1786                                      local_change_description);
1787  if (!*printed_description)
1788    SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1789                                _("Tree conflict on '%s':\n%s\n"),
1790                                svn_cl__local_style_skip_ancestor(
1791                                  path_prefix, local_abspath, scratch_pool),
1792                                conflict_description));
1793
1794  SVN_ERR(build_tree_conflict_options(&tree_conflict_options,
1795                                      &possible_moved_to_repos_relpaths,
1796                                      &possible_moved_to_abspaths,
1797                                      &all_options_are_dumb,
1798                                      conflict, ctx,
1799                                      scratch_pool, scratch_pool));
1800
1801  /* Try a recommended resolution option before prompting. */
1802  recommended_option = find_recommended_option(tree_conflict_options);
1803  if (recommended_option)
1804    {
1805      svn_error_t *err;
1806      apr_status_t root_cause;
1807
1808      SVN_ERR(svn_cmdline_printf(scratch_pool,
1809                                 _("Applying recommended resolution '%s':\n"),
1810                                 recommended_option->label));
1811
1812      err = mark_conflict_resolved(conflict, recommended_option->choice,
1813                                   FALSE, NULL, TRUE,
1814                                   path_prefix, conflict_stats,
1815                                   ctx, scratch_pool);
1816      if (!err)
1817        {
1818          *resolved = TRUE;
1819          return SVN_NO_ERROR;
1820        }
1821
1822      root_cause = svn_error_root_cause(err)->apr_err;
1823      if (root_cause != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE &&
1824          root_cause != SVN_ERR_WC_OBSTRUCTED_UPDATE &&
1825          root_cause != SVN_ERR_WC_FOUND_CONFLICT)
1826        return svn_error_trace(err);
1827
1828      /* Fall back to interactive prompting. */
1829      svn_error_clear(err);
1830    }
1831
1832  if (all_options_are_dumb)
1833    SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1834                                _("\nSubversion is not smart enough to resolve "
1835                                  "this tree conflict automatically!\nSee 'svn "
1836                                  "help resolve' for more information.\n\n")));
1837
1838  iterpool = svn_pool_create(scratch_pool);
1839  while (1)
1840    {
1841      const client_option_t *opt;
1842
1843      svn_pool_clear(iterpool);
1844
1845      if (!repos_move_target_chosen &&
1846          possible_moved_to_repos_relpaths &&
1847          possible_moved_to_repos_relpaths->nelts > 1)
1848        SVN_ERR(svn_cmdline_printf(scratch_pool,
1849                  _("Ambiguous move destinations exist in the repository; "
1850                    "try the 'd' option\n")));
1851      if (!wc_move_target_chosen && possible_moved_to_abspaths &&
1852          possible_moved_to_abspaths->nelts > 1)
1853        SVN_ERR(svn_cmdline_printf(scratch_pool,
1854                  _("Ambiguous move destinations exist in the working copy; "
1855                    "try the 'w' option\n")));
1856
1857      SVN_ERR(prompt_user(&opt, tree_conflict_options, NULL,
1858                          conflict_description, pb, iterpool));
1859      *printed_description = TRUE;
1860      if (! opt)
1861        continue;
1862
1863      if (strcmp(opt->code, "q") == 0)
1864        {
1865          option_id = opt->choice;
1866          *quit = TRUE;
1867          break;
1868        }
1869      else if (strcmp(opt->code, "d") == 0)
1870        {
1871          int preferred_move_target_idx;
1872          apr_array_header_t *options;
1873          svn_client_conflict_option_t *option;
1874
1875          SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,
1876                                          possible_moved_to_repos_relpaths,
1877                                          FALSE,
1878                                          pb, local_abspath, ctx, iterpool));
1879
1880          /* Update preferred move target path. */
1881          SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options,
1882                                                                  conflict,
1883                                                                  ctx,
1884                                                                  iterpool,
1885                                                                  iterpool));
1886          SVN_ERR(find_conflict_option_with_repos_move_targets(
1887            &option, options, iterpool));
1888          if (option)
1889            {
1890              SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath2(
1891                        option, preferred_move_target_idx, ctx, iterpool));
1892              repos_move_target_chosen = TRUE;
1893              wc_move_target_chosen = FALSE;
1894
1895              /* Update option description. */
1896              SVN_ERR(build_tree_conflict_options(
1897                        &tree_conflict_options,
1898                        &possible_moved_to_repos_relpaths,
1899                        &possible_moved_to_abspaths,
1900                        NULL, conflict, ctx,
1901                        scratch_pool, scratch_pool));
1902
1903              /* Update conflict description. */
1904              SVN_ERR(svn_client_conflict_tree_get_description(
1905                       &incoming_change_description, &local_change_description,
1906                       conflict, ctx, scratch_pool, scratch_pool));
1907              conflict_description = apr_psprintf(scratch_pool, "%s\n%s",
1908                                                  incoming_change_description,
1909                                                  local_change_description);
1910            }
1911          continue;
1912        }
1913      else if (strcmp(opt->code, "w") == 0)
1914        {
1915          int preferred_move_target_idx;
1916          apr_array_header_t *options;
1917          svn_client_conflict_option_t *option;
1918
1919          SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,
1920                                           possible_moved_to_abspaths, TRUE,
1921                                           pb, local_abspath, ctx, iterpool));
1922
1923          /* Update preferred move target path. */
1924          SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options,
1925                                                                  conflict,
1926                                                                  ctx,
1927                                                                  iterpool,
1928                                                                  iterpool));
1929          SVN_ERR(find_conflict_option_with_working_copy_move_targets(
1930            &option, options, iterpool));
1931          if (option)
1932            {
1933              SVN_ERR(svn_client_conflict_option_set_moved_to_abspath2(
1934                        option, preferred_move_target_idx, ctx, iterpool));
1935              wc_move_target_chosen = TRUE;
1936
1937              /* Update option description. */
1938              SVN_ERR(build_tree_conflict_options(
1939                        &tree_conflict_options,
1940                        &possible_moved_to_repos_relpaths,
1941                        &possible_moved_to_abspaths,
1942                        NULL, conflict, ctx,
1943                        scratch_pool, scratch_pool));
1944            }
1945          continue;
1946        }
1947      else if (opt->choice != svn_client_conflict_option_undefined)
1948        {
1949          option_id = opt->choice;
1950          break;
1951        }
1952    }
1953  svn_pool_destroy(iterpool);
1954  if (option_id != svn_client_conflict_option_unspecified &&
1955      option_id != svn_client_conflict_option_postpone)
1956    {
1957      SVN_ERR(mark_conflict_resolved(conflict, option_id,
1958                                     FALSE, NULL, TRUE,
1959                                     path_prefix, conflict_stats,
1960                                     ctx, scratch_pool));
1961      *resolved = TRUE;
1962    }
1963  else
1964    {
1965      *resolved = FALSE;
1966      *postponed = (option_id == svn_client_conflict_option_postpone);
1967    }
1968
1969  return SVN_NO_ERROR;
1970}
1971
1972static svn_error_t *
1973resolve_conflict_interactively(svn_boolean_t *resolved,
1974                               svn_boolean_t *postponed,
1975                               svn_boolean_t *quit,
1976                               svn_boolean_t *external_failed,
1977                               svn_boolean_t *printed_summary,
1978                               svn_boolean_t *printed_description,
1979                               svn_client_conflict_t *conflict,
1980                               const char *editor_cmd,
1981                               apr_hash_t *config,
1982                               const char *path_prefix,
1983                               svn_cmdline_prompt_baton_t *pb,
1984                               svn_cl__conflict_stats_t *conflict_stats,
1985                               svn_client_ctx_t *ctx,
1986                               apr_pool_t *scratch_pool)
1987{
1988  svn_boolean_t text_conflicted;
1989  apr_array_header_t *props_conflicted;
1990  svn_boolean_t tree_conflicted;
1991  const svn_string_t *merged_propval;
1992
1993  SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
1994                                             &props_conflicted,
1995                                             &tree_conflicted,
1996                                             conflict,
1997                                             scratch_pool,
1998                                             scratch_pool));
1999
2000  /* Print a summary of conflicts before starting interactive resolution */
2001  if (! *printed_summary)
2002    {
2003      SVN_ERR(svn_cl__print_conflict_stats(conflict_stats, scratch_pool));
2004      *printed_summary = TRUE;
2005    }
2006
2007  *resolved = FALSE;
2008  if (text_conflicted
2009       && (svn_client_conflict_get_incoming_change(conflict) ==
2010           svn_wc_conflict_action_edit)
2011       && (svn_client_conflict_get_local_change(conflict) ==
2012           svn_wc_conflict_reason_edited))
2013    SVN_ERR(handle_text_conflict(resolved, postponed, quit, printed_description,
2014                                 conflict, path_prefix, pb, editor_cmd, config,
2015                                 conflict_stats, ctx, scratch_pool));
2016  if (props_conflicted->nelts > 0)
2017    SVN_ERR(handle_prop_conflicts(resolved, postponed, quit, &merged_propval,
2018                                  path_prefix, pb, editor_cmd, config, conflict,
2019                                  conflict_stats, ctx, scratch_pool, scratch_pool));
2020  if (tree_conflicted)
2021    SVN_ERR(handle_tree_conflict(resolved, postponed, quit, printed_description,
2022                                 conflict, path_prefix, pb, conflict_stats, ctx,
2023                                 scratch_pool));
2024
2025  return SVN_NO_ERROR;
2026}
2027
2028svn_error_t *
2029svn_cl__resolve_conflict(svn_boolean_t *quit,
2030                         svn_boolean_t *external_failed,
2031                         svn_boolean_t *printed_summary,
2032                         svn_client_conflict_t *conflict,
2033                         svn_cl__accept_t accept_which,
2034                         const char *editor_cmd,
2035                         const char *path_prefix,
2036                         svn_cmdline_prompt_baton_t *pb,
2037                         svn_cl__conflict_stats_t *conflict_stats,
2038                         svn_client_ctx_t *ctx,
2039                         apr_pool_t *scratch_pool)
2040{
2041  svn_boolean_t text_conflicted;
2042  apr_array_header_t *props_conflicted;
2043  svn_boolean_t tree_conflicted;
2044  const char *local_abspath;
2045  svn_client_conflict_option_id_t option_id;
2046
2047  SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
2048                                             &props_conflicted,
2049                                             &tree_conflicted,
2050                                             conflict,
2051                                             scratch_pool,
2052                                             scratch_pool));
2053  local_abspath = svn_client_conflict_get_local_abspath(conflict);
2054
2055  if (accept_which == svn_cl__accept_unspecified)
2056    {
2057      option_id = svn_client_conflict_option_unspecified;
2058    }
2059  else if (accept_which == svn_cl__accept_postpone)
2060    {
2061      option_id = svn_client_conflict_option_postpone;
2062    }
2063  else if (accept_which == svn_cl__accept_base)
2064    {
2065      option_id = svn_client_conflict_option_base_text;
2066    }
2067  else if (accept_which == svn_cl__accept_working)
2068    {
2069      option_id = svn_client_conflict_option_merged_text;
2070
2071      if (text_conflicted)
2072        {
2073          const char *mime_type =
2074            svn_client_conflict_text_get_mime_type(conflict);
2075
2076          /* There is no merged text for binary conflicts, behave as
2077           * if 'mine-full' was chosen. */
2078          if (mime_type && svn_mime_type_is_binary(mime_type))
2079            option_id = svn_client_conflict_option_working_text;
2080        }
2081      else if (tree_conflicted)
2082        {
2083          /* For tree conflicts, map 'working' to 'accept current working
2084           * copy state'. */
2085          option_id = svn_client_conflict_option_accept_current_wc_state;
2086        }
2087    }
2088  else if (accept_which == svn_cl__accept_theirs_conflict)
2089    {
2090      option_id = svn_client_conflict_option_incoming_text_where_conflicted;
2091    }
2092  else if (accept_which == svn_cl__accept_mine_conflict)
2093    {
2094      option_id = svn_client_conflict_option_working_text_where_conflicted;
2095
2096      if (tree_conflicted)
2097        {
2098          svn_wc_operation_t operation;
2099
2100          operation = svn_client_conflict_get_operation(conflict);
2101          if (operation == svn_wc_operation_update ||
2102              operation == svn_wc_operation_switch)
2103            {
2104              svn_wc_conflict_reason_t reason;
2105
2106              reason = svn_client_conflict_get_local_change(conflict);
2107              if (reason == svn_wc_conflict_reason_moved_away)
2108                {
2109                  /* Map 'mine-conflict' to 'update move destination'. */
2110                  option_id =
2111                    svn_client_conflict_option_update_move_destination;
2112                }
2113              else if (reason == svn_wc_conflict_reason_deleted ||
2114                       reason == svn_wc_conflict_reason_replaced)
2115                {
2116                  svn_wc_conflict_action_t action;
2117                  svn_node_kind_t node_kind;
2118
2119                  action = svn_client_conflict_get_incoming_change(conflict);
2120                  node_kind =
2121                    svn_client_conflict_tree_get_victim_node_kind(conflict);
2122
2123                  if (action == svn_wc_conflict_action_edit &&
2124                      node_kind == svn_node_dir)
2125                    {
2126                      /* Map 'mine-conflict' to 'update any moved away
2127                       * children'. */
2128                      option_id =
2129                        svn_client_conflict_option_update_any_moved_away_children;
2130                    }
2131                }
2132            }
2133        }
2134    }
2135  else if (accept_which == svn_cl__accept_theirs_full)
2136    {
2137      option_id = svn_client_conflict_option_incoming_text;
2138    }
2139  else if (accept_which == svn_cl__accept_mine_full)
2140    {
2141      option_id = svn_client_conflict_option_working_text;
2142    }
2143  else if (accept_which == svn_cl__accept_edit)
2144    {
2145      option_id = svn_client_conflict_option_unspecified;
2146
2147      if (local_abspath)
2148        {
2149          if (*external_failed)
2150            {
2151              option_id = svn_client_conflict_option_postpone;
2152            }
2153          else
2154            {
2155              svn_error_t *err;
2156
2157              err = svn_cmdline__edit_file_externally(local_abspath,
2158                                                      editor_cmd,
2159                                                      ctx->config,
2160                                                      scratch_pool);
2161              if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR ||
2162                          err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
2163                {
2164                  char buf[1024];
2165                  const char *message;
2166
2167                  message = svn_err_best_message(err, buf, sizeof(buf));
2168                  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
2169                                              message));
2170                  svn_error_clear(err);
2171                  *external_failed = TRUE;
2172                }
2173              else if (err)
2174                return svn_error_trace(err);
2175              option_id = svn_client_conflict_option_merged_text;
2176            }
2177        }
2178    }
2179  else if (accept_which == svn_cl__accept_launch)
2180    {
2181      const char *base_abspath = NULL;
2182      const char *my_abspath = NULL;
2183      const char *their_abspath = NULL;
2184
2185      option_id = svn_client_conflict_option_unspecified;
2186
2187      if (text_conflicted)
2188        SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
2189                                                      &base_abspath,
2190                                                      &their_abspath,
2191                                                      conflict, scratch_pool,
2192                                                      scratch_pool));
2193
2194      if (base_abspath && their_abspath && my_abspath && local_abspath)
2195        {
2196          if (*external_failed)
2197            {
2198              option_id = svn_client_conflict_option_postpone;
2199            }
2200          else
2201            {
2202              svn_boolean_t remains_in_conflict;
2203              svn_error_t *err;
2204
2205              err = svn_cl__merge_file_externally(base_abspath, their_abspath,
2206                                                  my_abspath, local_abspath,
2207                                                  local_abspath, ctx->config,
2208                                                  &remains_in_conflict,
2209                                                  scratch_pool);
2210              if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL ||
2211                          err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
2212                {
2213                  char buf[1024];
2214                  const char *message;
2215
2216                  message = svn_err_best_message(err, buf, sizeof(buf));
2217                  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
2218                                              message));
2219                  *external_failed = TRUE;
2220                  return svn_error_trace(err);
2221                }
2222              else if (err)
2223                return svn_error_trace(err);
2224
2225              if (remains_in_conflict)
2226                option_id = svn_client_conflict_option_postpone;
2227              else
2228                option_id = svn_client_conflict_option_merged_text;
2229            }
2230        }
2231    }
2232  else if (accept_which == svn_cl__accept_recommended)
2233    {
2234      svn_client_conflict_option_id_t recommended_id;
2235
2236      if (tree_conflicted)
2237        SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx,
2238                                                     scratch_pool));
2239      recommended_id = svn_client_conflict_get_recommended_option_id(conflict);
2240      if (recommended_id != svn_client_conflict_option_unspecified)
2241        option_id = recommended_id;
2242      else
2243        option_id = svn_client_conflict_option_postpone;
2244    }
2245  else
2246    SVN_ERR_MALFUNCTION();
2247
2248  /* If we are in interactive mode and either the user gave no --accept
2249   * option or the option did not apply, then prompt. */
2250  if (option_id == svn_client_conflict_option_unspecified)
2251    {
2252      svn_boolean_t resolved = FALSE;
2253      svn_boolean_t postponed = FALSE;
2254      svn_boolean_t printed_description = FALSE;
2255      svn_error_t *err;
2256      apr_pool_t *iterpool;
2257
2258      *quit = FALSE;
2259
2260      iterpool = svn_pool_create(scratch_pool);
2261      while (!resolved && !postponed && !*quit)
2262        {
2263          svn_pool_clear(iterpool);
2264          err = resolve_conflict_interactively(&resolved, &postponed, quit,
2265                                               external_failed,
2266                                               printed_summary,
2267                                               &printed_description,
2268                                               conflict,
2269                                               editor_cmd, ctx->config,
2270                                               path_prefix, pb,
2271                                               conflict_stats, ctx,
2272                                               iterpool);
2273          if (err && err->apr_err == SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE)
2274            {
2275              /* Conflict resolution has failed. Let the user try again.
2276               * It is always possible to break out of this loop with
2277               * the 'quit' or 'postpone' options. */
2278              svn_handle_warning2(stderr, err, "svn: ");
2279              svn_error_clear(err);
2280              err = SVN_NO_ERROR;
2281            }
2282          SVN_ERR(err);
2283        }
2284      svn_pool_destroy(iterpool);
2285    }
2286  else if (option_id != svn_client_conflict_option_postpone)
2287    SVN_ERR(mark_conflict_resolved(conflict, option_id,
2288                                   text_conflicted,
2289                                   props_conflicted->nelts > 0 ? "" : NULL,
2290                                   tree_conflicted,
2291                                   path_prefix, conflict_stats,
2292                                   ctx, scratch_pool));
2293
2294  return SVN_NO_ERROR;
2295}
2296