]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svn/conflict-callbacks.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / svn / conflict-callbacks.c
1 /*
2  * conflict-callbacks.c: conflict resolution callbacks specific to the
3  * commandline client.
4  *
5  * ====================================================================
6  *    Licensed to the Apache Software Foundation (ASF) under one
7  *    or more contributor license agreements.  See the NOTICE file
8  *    distributed with this work for additional information
9  *    regarding copyright ownership.  The ASF licenses this file
10  *    to you under the Apache License, Version 2.0 (the
11  *    "License"); you may not use this file except in compliance
12  *    with the License.  You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  *    Unless required by applicable law or agreed to in writing,
17  *    software distributed under the License is distributed on an
18  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19  *    KIND, either express or implied.  See the License for the
20  *    specific language governing permissions and limitations
21  *    under the License.
22  * ====================================================================
23  */
24
25 #include <apr_xlate.h>  /* for APR_LOCALE_CHARSET */
26
27 #define APR_WANT_STRFUNC
28 #include <apr_want.h>
29
30 #include "svn_hash.h"
31 #include "svn_cmdline.h"
32 #include "svn_client.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_types.h"
35 #include "svn_pools.h"
36 #include "svn_sorts.h"
37 #include "svn_utf.h"
38
39 #include "cl.h"
40 #include "cl-conflicts.h"
41
42 #include "private/svn_cmdline_private.h"
43
44 #include "svn_private_config.h"
45
46 #define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0])))
47
48 \f
49
50 svn_cl__accept_t
51 svn_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. */
91 static svn_error_t *
92 show_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. */
173 static svn_error_t *
174 show_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  */
216 static svn_error_t *
217 merge_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  */
265 static svn_error_t *
266 show_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. */
292 static svn_error_t *
293 open_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. */
333 static svn_error_t *
334 edit_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. */
377 typedef 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
385 typedef 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.  */
397 static 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
444   { NULL }
445 };
446
447 /* Extra resolver options offered by 'svn' for any conflict. */
448 static const client_option_t extra_resolver_options[] =
449 {
450   /* Translators: keep long_desc below 70 characters (wrap with a left
451      margin of 9 spaces if needed) */
452   { "q",  N_("Quit resolution"),  N_("postpone all remaining conflicts"),
453                                   svn_client_conflict_option_postpone },
454   { NULL }
455 };
456
457
458 /* Additional resolver options offered by 'svn' for a text conflict. */
459 static const client_option_t extra_resolver_options_text[] =
460 {
461   /* Translators: keep long_desc below 70 characters (wrap with a left
462      margin of 9 spaces if needed) */
463   { "e",  N_("Edit file"),        N_("change merged file in an editor"),
464                                   svn_client_conflict_option_undefined,
465                                   SVN_CL__ACCEPT_EDIT },
466   { "df", N_("Show diff"),        N_("show all changes made to merged file"),
467                                   svn_client_conflict_option_undefined},
468   { "dc", N_("Display conflict"), N_("show all conflicts "
469                                      "(ignoring merged version)"),
470                                   svn_client_conflict_option_undefined },
471   { "m",  N_("Merge"),            N_("use merge tool to resolve conflict"),
472                                   svn_client_conflict_option_undefined },
473   { "l",  N_("Launch tool"),      N_("launch external merge tool to resolve "
474                                      "conflict"),
475                                   svn_client_conflict_option_undefined,
476                                   SVN_CL__ACCEPT_LAUNCH },
477   { "i",  N_("Internal merge tool"), N_("use built-in merge tool to "
478                                      "resolve conflict"),
479                                   svn_client_conflict_option_undefined },
480   { "s",  N_("Show all options"), N_("show this list (also 'h', '?')"),
481                                   svn_client_conflict_option_undefined },
482   { NULL }
483 };
484
485 /* Additional resolver options offered by 'svn' for a property conflict. */
486 static const client_option_t extra_resolver_options_prop[] =
487 {
488   /* Translators: keep long_desc below 70 characters (wrap with a left
489      margin of 9 spaces if needed) */
490   { "dc", N_("Display conflict"), N_("show conflicts in this property"),
491                                   svn_client_conflict_option_undefined },
492   { "e",  N_("Edit property"),    N_("change merged property value in an "
493                                      "editor"),
494                                   svn_client_conflict_option_undefined,
495                                   SVN_CL__ACCEPT_EDIT },
496   { "h",  N_("Help"),             N_("show this help (also '?')"),
497                                   svn_client_conflict_option_undefined },
498   { NULL }
499 };
500
501 /* Additional resolver options offered by 'svn' for a tree conflict. */
502 static const client_option_t extra_resolver_options_tree[] =
503 {
504   /* Translators: keep long_desc below 70 characters (wrap with a left
505      margin of 9 spaces if needed) */
506   { "d",  N_("Set repository move destination path"),
507           N_("pick repository move target from list of possible targets"),
508                                   svn_client_conflict_option_undefined },
509
510   { "w",  N_("Set working copy move destination path"),
511           N_("pick working copy move target from list of possible targets"),
512                                   svn_client_conflict_option_undefined },
513
514   { "h",  N_("Help"),             N_("show this help (also '?')"),
515                                   svn_client_conflict_option_undefined },
516
517   { NULL }
518 };
519
520
521 /* Return a pointer to the option description in OPTIONS matching the
522  * one- or two-character OPTION_CODE.  Return NULL if not found. */
523 static const client_option_t *
524 find_option(const apr_array_header_t *options,
525             const char *option_code)
526 {
527   int i;
528
529   for (i = 0; i < options->nelts; i++)
530     {
531       const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *);
532
533       /* Ignore code "" (blank lines) which is not a valid answer. */
534       if (opt->code[0] && strcmp(opt->code, option_code) == 0)
535         return opt;
536     }
537   return NULL;
538 }
539
540 /* Find the first recommended option in OPTIONS. */
541 static const client_option_t *
542 find_recommended_option(const apr_array_header_t *options)
543 {
544   int i;
545
546   for (i = 0; i < options->nelts; i++)
547     {
548       const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *);
549
550       /* Ignore code "" (blank lines) which is not a valid answer. */
551       if (opt->code[0] && opt->is_recommended)
552         return opt;
553     }
554   return NULL;
555 }
556
557 /* Return a pointer to the client_option_t in OPTIONS matching the ID of
558  * conflict option BUILTIN_OPTION. @a out will be set to NULL if the
559  * option was not found. */
560 static svn_error_t *
561 find_option_by_builtin(client_option_t **out,
562                        svn_client_conflict_t *conflict,
563                        const resolver_option_t *options,
564                        svn_client_conflict_option_t *builtin_option,
565                        apr_pool_t *result_pool,
566                        apr_pool_t *scratch_pool)
567 {
568   const resolver_option_t *opt;
569   svn_client_conflict_option_id_t id;
570   svn_client_conflict_option_id_t recommended_id;
571
572   id = svn_client_conflict_option_get_id(builtin_option);
573   recommended_id = svn_client_conflict_get_recommended_option_id(conflict);
574
575   for (opt = options; opt->code; opt++)
576     {
577       if (opt->choice == id)
578         {
579           client_option_t *client_opt;
580
581           client_opt = apr_pcalloc(result_pool, sizeof(*client_opt));
582           client_opt->choice = id;
583           client_opt->code = opt->code;
584           client_opt->label = svn_client_conflict_option_get_label(
585               builtin_option,
586               result_pool);
587           client_opt->long_desc = svn_client_conflict_option_get_description(
588                                     builtin_option,
589                                     result_pool);
590           client_opt->accept_arg = opt->accept_arg;
591           client_opt->is_recommended =
592             (recommended_id != svn_client_conflict_option_unspecified &&
593              id == recommended_id);
594
595           *out = client_opt;
596
597           return SVN_NO_ERROR;
598         }
599     }
600
601   *out = NULL;
602
603   return SVN_NO_ERROR;
604 }
605
606 /* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
607  * non-null, select only the options whose codes are mentioned in it. */
608 static const char *
609 prompt_string(const apr_array_header_t *options,
610               const char *const *option_codes,
611               apr_pool_t *pool)
612 {
613   const char *result = _("Select:");
614   int left_margin = svn_utf_cstring_utf8_width(result);
615   const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
616   int this_line_len = left_margin;
617   svn_boolean_t first = TRUE;
618   int i = 0;
619
620   while (1)
621     {
622       const client_option_t *opt;
623       const char *s;
624       int slen;
625
626       if (option_codes)
627         {
628           if (! *option_codes)
629             break;
630           opt = find_option(options, *option_codes++);
631           if (opt == NULL)
632             continue;
633         }
634       else
635         {
636           if (i >= options->nelts)
637             break;
638           opt = APR_ARRAY_IDX(options, i, client_option_t *);
639           i++;
640         }
641
642       if (! first)
643         result = apr_pstrcat(pool, result, ",", SVN_VA_NULL);
644       s = apr_psprintf(pool, " (%s) %s", opt->code,
645                        opt->label ? opt->label : opt->long_desc);
646       slen = svn_utf_cstring_utf8_width(s);
647       /* Break the line if adding the next option would make it too long */
648       if (this_line_len + slen > MAX_PROMPT_WIDTH)
649         {
650           result = apr_pstrcat(pool, result, line_sep, SVN_VA_NULL);
651           this_line_len = left_margin;
652         }
653       result = apr_pstrcat(pool, result, s, SVN_VA_NULL);
654       this_line_len += slen;
655       first = FALSE;
656     }
657   return apr_pstrcat(pool, result, ": ", SVN_VA_NULL);
658 }
659
660 /* Return a help string listing the OPTIONS. */
661 static svn_error_t *
662 help_string(const char **result,
663             const apr_array_header_t *options,
664             apr_pool_t *pool)
665 {
666   apr_pool_t *iterpool;
667   int i;
668
669   *result = "";
670   iterpool = svn_pool_create(pool);
671   for (i = 0; i < options->nelts; i++)
672     {
673       const client_option_t *opt;
674       svn_pool_clear(iterpool);
675
676       opt = APR_ARRAY_IDX(options, i,
677                           client_option_t *);
678
679       /* Append a line describing OPT, or a blank line if its code is "". */
680       if (opt->code[0])
681         {
682           const char *s = apr_psprintf(pool, "  (%s)", opt->code);
683
684           if (opt->accept_arg)
685             *result = apr_psprintf(pool, "%s%-6s - %s  [%s]\n",
686                                    *result, s, opt->long_desc,
687                                    opt->accept_arg);
688           else
689             *result = apr_psprintf(pool, "%s%-6s - %s\n", *result, s,
690                                    opt->long_desc);
691         }
692       else
693         {
694           *result = apr_pstrcat(pool, *result, "\n", SVN_VA_NULL);
695         }
696     }
697   svn_pool_destroy(iterpool);
698   *result = apr_pstrcat(pool, *result,
699                        _("Words in square brackets are the corresponding "
700                          "--accept option arguments.\n"),
701                        SVN_VA_NULL);
702   return SVN_NO_ERROR;
703 }
704
705 /* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
706  * in OPTIONS_TO_SHOW if that is non-null.  Set *OPT to point to the chosen
707  * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
708  * NULL if the answer was not one of them.
709  *
710  * If the answer is the (globally recognized) 'help' option, then display
711  * CONFLICT_DESCRIPTION (if not NULL) and help (on stderr) and return with
712  * *OPT == NULL.
713  */
714 static svn_error_t *
715 prompt_user(const client_option_t **opt,
716             const apr_array_header_t *conflict_options,
717             const char *const *options_to_show,
718             const char *conflict_description,
719             void *prompt_baton,
720             apr_pool_t *scratch_pool)
721 {
722   const char *prompt
723     = prompt_string(conflict_options, options_to_show, scratch_pool);
724   const char *answer;
725
726   SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
727   if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
728     {
729       const char *helpstr;
730
731       if (conflict_description)
732         SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
733                                     conflict_description));
734       SVN_ERR(help_string(&helpstr, conflict_options, scratch_pool));
735       SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", helpstr));
736       *opt = NULL;
737     }
738   else
739     {
740       *opt = find_option(conflict_options, answer);
741       if (! *opt)
742         {
743           SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
744                                       _("Unrecognized option.\n\n")));
745         }
746     }
747   return SVN_NO_ERROR;
748 }
749
750 /* Set *OPTIONS to an array of resolution options for CONFLICT. */
751 static svn_error_t *
752 build_text_conflict_options(apr_array_header_t **options,
753                             svn_client_conflict_t *conflict,
754                             svn_client_ctx_t *ctx,
755                             svn_boolean_t is_binary,
756                             apr_pool_t *result_pool,
757                             apr_pool_t *scratch_pool)
758 {
759   const client_option_t *o;
760   apr_array_header_t *builtin_options;
761   int nopt;
762   int i;
763   apr_pool_t *iterpool;
764
765   SVN_ERR(svn_client_conflict_text_get_resolution_options(&builtin_options,
766                                                           conflict, ctx,
767                                                           scratch_pool,
768                                                           scratch_pool));
769   nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options);
770   if (!is_binary)
771     nopt += ARRAY_LEN(extra_resolver_options_text);
772   *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
773
774   iterpool = svn_pool_create(scratch_pool);
775   for (i = 0; i < builtin_options->nelts; i++)
776     {
777       client_option_t *opt;
778       svn_client_conflict_option_t *builtin_option;
779
780       svn_pool_clear(iterpool);
781       builtin_option = APR_ARRAY_IDX(builtin_options, i,
782                                      svn_client_conflict_option_t *);
783       SVN_ERR(find_option_by_builtin(&opt, conflict,
784                                      builtin_resolver_options,
785                                      builtin_option,
786                                      result_pool,
787                                      iterpool));
788       if (opt == NULL)
789         continue; /* ### unknown option -- assign a code dynamically? */
790
791       APR_ARRAY_PUSH(*options, client_option_t *) = opt;
792     }
793
794   for (o = extra_resolver_options; o->code; o++)
795     APR_ARRAY_PUSH(*options, const client_option_t *) = o;
796   if (!is_binary)
797     {
798       for (o = extra_resolver_options_text; o->code; o++)
799         APR_ARRAY_PUSH(*options, const client_option_t *) = o;
800     }
801
802   svn_pool_destroy(iterpool);
803
804   return SVN_NO_ERROR;
805 }
806
807 /* Mark CONFLICT as resolved to resolution option with ID OPTION_ID.
808  * If TEXT_CONFLICTED is true, resolve text conflicts described by CONFLICT.
809  * IF PROPNAME is not NULL, mark the conflict in the specified property as
810  * resolved. If PROPNAME is "", mark all property conflicts described by
811  * CONFLICT as resolved.
812  * If TREE_CONFLICTED is true, resolve tree conflicts described by CONFLICT.
813  * Adjust CONFLICT_STATS as necessary (PATH_PREFIX is needed for this step). */
814 static svn_error_t *
815 mark_conflict_resolved(svn_client_conflict_t *conflict,
816                        svn_client_conflict_option_id_t option_id,
817                        svn_boolean_t text_conflicted,
818                        const char *propname,
819                        svn_boolean_t tree_conflicted,
820                        const char *path_prefix,
821                        svn_cl__conflict_stats_t *conflict_stats,
822                        svn_client_ctx_t *ctx,
823                        apr_pool_t *scratch_pool)
824 {
825   const char *local_relpath
826     = svn_cl__local_style_skip_ancestor(
827         path_prefix, svn_client_conflict_get_local_abspath(conflict),
828         scratch_pool);
829
830   if (text_conflicted)
831     {
832       SVN_ERR(svn_client_conflict_text_resolve_by_id(conflict, option_id,
833                                                      ctx, scratch_pool));
834       svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
835                                       svn_wc_conflict_kind_text);
836     }
837
838   if (propname)
839     {
840       SVN_ERR(svn_client_conflict_prop_resolve_by_id(conflict, propname,
841                                                      option_id, ctx,
842                                                      scratch_pool));
843       svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
844                                       svn_wc_conflict_kind_property);
845     }
846
847   if (tree_conflicted)
848     {
849       SVN_ERR(svn_client_conflict_tree_resolve_by_id(conflict, option_id,
850                                                      ctx, scratch_pool));
851       svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
852                                       svn_wc_conflict_kind_tree);
853     }
854
855   return SVN_NO_ERROR;
856 }
857
858 /* Ask the user what to do about the text conflict described by CONFLICT
859  * and either resolve the conflict accordingly or postpone resolution.
860  * SCRATCH_POOL is used for temporary allocations. */
861 static svn_error_t *
862 handle_text_conflict(svn_boolean_t *resolved,
863                      svn_boolean_t *postponed,
864                      svn_boolean_t *quit,
865                      svn_boolean_t *printed_description,
866                      svn_client_conflict_t *conflict,
867                      const char *path_prefix,
868                      svn_cmdline_prompt_baton_t *pb,
869                      const char *editor_cmd,
870                      apr_hash_t *config,
871                      svn_cl__conflict_stats_t *conflict_stats,
872                      svn_client_ctx_t *ctx,
873                      apr_pool_t *scratch_pool)
874 {
875   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
876   svn_boolean_t diff_allowed = FALSE;
877   /* Have they done something that might have affected the merged file? */
878   svn_boolean_t performed_edit = FALSE;
879   /* Have they done *something* (edit, look at diff, etc) to
880      give them a rational basis for choosing (r)esolved? */
881   svn_boolean_t knows_something = FALSE;
882   const char *local_relpath;
883   const char *local_abspath = svn_client_conflict_get_local_abspath(conflict);
884   const char *mime_type = svn_client_conflict_text_get_mime_type(conflict);
885   svn_boolean_t is_binary = mime_type ? svn_mime_type_is_binary(mime_type)
886                                       : FALSE;
887   const char *base_abspath;
888   const char *my_abspath;
889   const char *their_abspath;
890   const char *merged_abspath = svn_client_conflict_get_local_abspath(conflict);
891   apr_array_header_t *text_conflict_options;
892   svn_client_conflict_option_id_t option_id; 
893
894   option_id = svn_client_conflict_option_unspecified;
895
896   SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
897                                                 &base_abspath, &their_abspath,
898                                                 conflict, scratch_pool,
899                                                 scratch_pool));
900
901   local_relpath = svn_cl__local_style_skip_ancestor(path_prefix,
902                                                     local_abspath,
903                                                     scratch_pool);
904
905   if (!*printed_description)
906     {
907       if (is_binary)
908         SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
909                                     _("Merge conflict discovered in binary "
910                                       "file '%s'.\n"),
911                                     local_relpath));
912       else
913         SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
914                                     _("Merge conflict discovered in file '%s'.\n"),
915                                     local_relpath));
916       *printed_description = TRUE;
917     }
918
919   /* ### TODO This whole feature availability check is grossly outdated.
920      DIFF_ALLOWED needs either to be redefined or to go away.
921    */
922
923   /* Diffing can happen between base and merged, to show conflict
924      markers to the user (this is the typical 3-way merge
925      scenario), or if no base is available, we can show a diff
926      between mine and theirs. */
927   if (!is_binary &&
928       ((merged_abspath && base_abspath)
929       || (!base_abspath && my_abspath && their_abspath)))
930     diff_allowed = TRUE;
931
932   SVN_ERR(build_text_conflict_options(&text_conflict_options, conflict, ctx,
933                                       is_binary, scratch_pool, scratch_pool));
934   while (TRUE)
935     {
936       const char *suggested_options[9]; /* filled statically below */
937       const char **next_option = suggested_options;
938       const client_option_t *opt;
939
940       svn_pool_clear(iterpool);
941
942       *next_option++ = "p";
943       if (diff_allowed)
944         {
945           /* We need one more path for this feature. */
946           if (my_abspath)
947             *next_option++ = "df";
948
949           *next_option++ = "e";
950
951           /* We need one more path for this feature. */
952           if (my_abspath)
953             *next_option++ = "m";
954
955           if (knows_something)
956             *next_option++ = "r";
957         }
958       else
959         {
960           if (knows_something || is_binary)
961             *next_option++ = "r";
962
963           /* The 'mine-full' option selects the ".mine" file for texts or
964            * the current working directory file for binary files. */
965           if (my_abspath || is_binary)
966             *next_option++ = "mf";
967
968           *next_option++ = "tf";
969         }
970       *next_option++ = "s";
971       *next_option++ = NULL;
972
973       SVN_ERR(prompt_user(&opt, text_conflict_options, suggested_options,
974                           NULL, pb, iterpool));
975       if (! opt)
976         continue;
977
978       if (strcmp(opt->code, "q") == 0)
979         {
980           option_id = opt->choice;
981           *quit = TRUE;
982           break;
983         }
984       else if (strcmp(opt->code, "s") == 0)
985         {
986           const char *helpstr;
987
988           SVN_ERR(help_string(&helpstr, text_conflict_options, iterpool));
989           SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
990                                       helpstr));
991         }
992       else if (strcmp(opt->code, "dc") == 0)
993         {
994           if (is_binary)
995             {
996               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
997                                           _("Invalid option; cannot "
998                                             "display conflicts for a "
999                                             "binary file.\n\n")));
1000               continue;
1001             }
1002           else if (! (my_abspath && base_abspath && their_abspath))
1003             {
1004               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1005                                           _("Invalid option; original "
1006                                             "files not available.\n\n")));
1007               continue;
1008             }
1009           SVN_ERR(show_conflicts(conflict,
1010                                  pb->cancel_func,
1011                                  pb->cancel_baton,
1012                                  iterpool));
1013           knows_something = TRUE;
1014         }
1015       else if (strcmp(opt->code, "df") == 0)
1016         {
1017           /* Re-check preconditions. */
1018           if (! diff_allowed || ! my_abspath)
1019             {
1020               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1021                              _("Invalid option; there's no "
1022                                 "merged version to diff.\n\n")));
1023               continue;
1024             }
1025
1026           SVN_ERR(show_diff(conflict, merged_abspath, path_prefix,
1027                             pb->cancel_func, pb->cancel_baton,
1028                             iterpool));
1029           knows_something = TRUE;
1030         }
1031       else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
1032         {
1033           SVN_ERR(open_editor(&performed_edit, merged_abspath, editor_cmd,
1034                               config, iterpool));
1035           if (performed_edit)
1036             knows_something = TRUE;
1037         }
1038       else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
1039                strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
1040         {
1041           svn_error_t *err;
1042
1043           /* Re-check preconditions. */
1044           if (! my_abspath)
1045             {
1046               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1047                              _("Invalid option; there's no "
1048                                 "base path to merge.\n\n")));
1049               continue;
1050             }
1051
1052           err = svn_cl__merge_file_externally(base_abspath,
1053                                               their_abspath,
1054                                               my_abspath,
1055                                               merged_abspath,
1056                                               local_abspath, config,
1057                                               NULL, iterpool);
1058           if (err)
1059             {
1060               if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
1061                 {
1062                   svn_boolean_t remains_in_conflict = TRUE;
1063
1064                   /* Try the internal merge tool. */
1065                   svn_error_clear(err);
1066                   SVN_ERR(svn_cl__merge_file(&remains_in_conflict,
1067                                              base_abspath,
1068                                              their_abspath,
1069                                              my_abspath,
1070                                              merged_abspath,
1071                                              local_abspath,
1072                                              path_prefix,
1073                                              editor_cmd,
1074                                              config,
1075                                              pb->cancel_func,
1076                                              pb->cancel_baton,
1077                                              iterpool));
1078                   knows_something = !remains_in_conflict;
1079                 }
1080               else if (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
1081                 {
1082                   char buf[1024];
1083                   const char *message;
1084
1085                   message = svn_err_best_message(err, buf, sizeof(buf));
1086                   SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1087                                               "%s\n", message));
1088                   svn_error_clear(err);
1089                   continue;
1090                 }
1091               else
1092                 return svn_error_trace(err);
1093             }
1094           else
1095             {
1096               /* The external merge tool's exit code was either 0 or 1.
1097                * The tool may leave the file conflicted by exiting with
1098                * exit code 1, and we allow the user to mark the conflict
1099                * resolved in this case. */
1100               performed_edit = TRUE;
1101               knows_something = TRUE;
1102             }
1103         }
1104       else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
1105         {
1106           /* ### This check should be earlier as it's nasty to offer an option
1107            *     and then when the user chooses it say 'Invalid option'. */
1108           /* ### 'merged_abspath' shouldn't be necessary *before* we launch the
1109            *     resolver: it should be the *result* of doing so. */
1110           if (base_abspath && their_abspath && my_abspath && merged_abspath)
1111             {
1112               svn_error_t *err;
1113               char buf[1024];
1114               const char *message;
1115
1116               err = svn_cl__merge_file_externally(base_abspath,
1117                                                   their_abspath,
1118                                                   my_abspath,
1119                                                   merged_abspath,
1120                                                   local_abspath,
1121                                                   config, NULL, iterpool);
1122               if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL ||
1123                           err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
1124                 {
1125                   message = svn_err_best_message(err, buf, sizeof(buf));
1126                   SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n",
1127                                               message));
1128                   svn_error_clear(err);
1129                 }
1130               else if (err)
1131                 return svn_error_trace(err);
1132               else
1133                 performed_edit = TRUE;
1134
1135               if (performed_edit)
1136                 knows_something = TRUE;
1137             }
1138           else
1139             SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1140                                         _("Invalid option.\n\n")));
1141         }
1142       else if (strcmp(opt->code, "i") == 0)
1143         {
1144           svn_boolean_t remains_in_conflict = TRUE;
1145
1146           SVN_ERR(svn_cl__merge_file(&remains_in_conflict,
1147                                      base_abspath,
1148                                      their_abspath,
1149                                      my_abspath,
1150                                      merged_abspath,
1151                                      local_abspath,
1152                                      path_prefix,
1153                                      editor_cmd,
1154                                      config,
1155                                      pb->cancel_func,
1156                                      pb->cancel_baton,
1157                                      iterpool));
1158
1159           if (!remains_in_conflict)
1160             knows_something = TRUE;
1161         }
1162       else if (opt->choice != svn_client_conflict_option_undefined)
1163         {
1164           if ((opt->choice == svn_client_conflict_option_working_text_where_conflicted
1165                || opt->choice == svn_client_conflict_option_incoming_text_where_conflicted)
1166               && is_binary)
1167             {
1168               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1169                                           _("Invalid option; cannot choose "
1170                                             "based on conflicts in a "
1171                                             "binary file.\n\n")));
1172               continue;
1173             }
1174
1175           /* We only allow the user accept the merged version of
1176              the file if they've edited it, or at least looked at
1177              the diff. */
1178           if (opt->choice == svn_client_conflict_option_merged_text
1179               && ! knows_something && diff_allowed)
1180             {
1181               SVN_ERR(svn_cmdline_fprintf(
1182                         stderr, iterpool,
1183                         _("Invalid option; use diff/edit/merge/launch "
1184                           "before choosing 'mark resolved'.\n\n")));
1185               continue;
1186             }
1187
1188           option_id = opt->choice;
1189           break;
1190         }
1191     }
1192   svn_pool_destroy(iterpool);
1193
1194   if (option_id != svn_client_conflict_option_unspecified &&
1195       option_id != svn_client_conflict_option_postpone)
1196     {
1197       SVN_ERR(mark_conflict_resolved(conflict, option_id,
1198                                      TRUE, NULL, FALSE,
1199                                      path_prefix, conflict_stats,
1200                                      ctx, scratch_pool));
1201       *resolved = TRUE;
1202     }
1203   else
1204     {
1205       *resolved = FALSE;
1206       *postponed = (option_id == svn_client_conflict_option_postpone);
1207     }
1208
1209   return SVN_NO_ERROR;
1210 }
1211
1212 /* Set *OPTIONS to an array of resolution options for CONFLICT. */
1213 static svn_error_t *
1214 build_prop_conflict_options(apr_array_header_t **options,
1215                             svn_client_conflict_t *conflict,
1216                             svn_client_ctx_t *ctx,
1217                             apr_pool_t *result_pool,
1218                             apr_pool_t *scratch_pool)
1219 {
1220   const client_option_t *o;
1221   apr_array_header_t *builtin_options;
1222   int nopt;
1223   int i;
1224   apr_pool_t *iterpool;
1225
1226   SVN_ERR(svn_client_conflict_prop_get_resolution_options(&builtin_options,
1227                                                           conflict, ctx,
1228                                                           scratch_pool,
1229                                                           scratch_pool));
1230   nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options) +
1231            ARRAY_LEN(extra_resolver_options_prop);
1232   *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
1233
1234   iterpool = svn_pool_create(scratch_pool);
1235   for (i = 0; i < builtin_options->nelts; i++)
1236     {
1237       client_option_t *opt;
1238       svn_client_conflict_option_t *builtin_option;
1239
1240       svn_pool_clear(iterpool);
1241       builtin_option = APR_ARRAY_IDX(builtin_options, i,
1242                                      svn_client_conflict_option_t *);
1243       SVN_ERR(find_option_by_builtin(&opt, conflict,
1244                                      builtin_resolver_options,
1245                                      builtin_option,
1246                                      result_pool,
1247                                      iterpool));
1248       if (opt == NULL)
1249         continue; /* ### unknown option -- assign a code dynamically? */
1250
1251       APR_ARRAY_PUSH(*options, client_option_t *) = opt;
1252     }
1253
1254   svn_pool_destroy(iterpool);
1255
1256   for (o = extra_resolver_options; o->code; o++)
1257     APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1258   for (o = extra_resolver_options_prop; o->code; o++)
1259     APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1260
1261   return SVN_NO_ERROR;
1262 }
1263
1264 /* Ask the user what to do about the conflicted property PROPNAME described
1265  * by CONFLICT and return the corresponding resolution option in *OPTION.
1266  * SCRATCH_POOL is used for temporary allocations. */
1267 static svn_error_t *
1268 handle_one_prop_conflict(svn_client_conflict_option_t **option,
1269                          svn_boolean_t *quit,
1270                          const char *path_prefix,
1271                          svn_cmdline_prompt_baton_t *pb,
1272                          const char *editor_cmd,
1273                          apr_hash_t *config,
1274                          svn_client_conflict_t *conflict,
1275                          const char *propname,
1276                          svn_client_ctx_t *ctx,
1277                          apr_pool_t *result_pool,
1278                          apr_pool_t *scratch_pool)
1279 {
1280   apr_pool_t *iterpool;
1281   const char *description;
1282   const svn_string_t *merged_propval = NULL;
1283   svn_boolean_t resolved_allowed = FALSE;
1284   const svn_string_t *base_propval;
1285   const svn_string_t *my_propval;
1286   const svn_string_t *their_propval;
1287   apr_array_header_t *resolution_options;
1288   apr_array_header_t *prop_conflict_options;
1289
1290   SVN_ERR(svn_client_conflict_prop_get_propvals(NULL, &my_propval,
1291                                                 &base_propval, &their_propval,
1292                                                 conflict, propname,
1293                                                 scratch_pool));
1294
1295   SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1296                               _("Conflict for property '%s' discovered"
1297                                 " on '%s'.\n"),
1298                               propname,
1299                               svn_cl__local_style_skip_ancestor(
1300                                 path_prefix,
1301                                 svn_client_conflict_get_local_abspath(conflict),
1302                                 scratch_pool)));
1303   SVN_ERR(svn_client_conflict_prop_get_description(&description, conflict,
1304                                                    scratch_pool, scratch_pool));
1305   SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", description));
1306
1307   SVN_ERR(svn_client_conflict_prop_get_resolution_options(&resolution_options,
1308                                                           conflict, ctx,
1309                                                           result_pool,
1310                                                           scratch_pool));
1311   SVN_ERR(build_prop_conflict_options(&prop_conflict_options, conflict, ctx,
1312                                       scratch_pool, scratch_pool));
1313   iterpool = svn_pool_create(scratch_pool);
1314   while (TRUE)
1315     {
1316       const client_option_t *opt;
1317       const char *suggested_options[9]; /* filled statically below */
1318       const char **next_option = suggested_options;
1319
1320       *next_option++ = "p";
1321       *next_option++ = "mf";
1322       *next_option++ = "tf";
1323       *next_option++ = "dc";
1324       *next_option++ = "e";
1325       if (resolved_allowed)
1326         *next_option++ = "r";
1327       *next_option++ = "q";
1328       *next_option++ = "h";
1329       *next_option++ = NULL;
1330
1331       svn_pool_clear(iterpool);
1332
1333       SVN_ERR(prompt_user(&opt, prop_conflict_options, suggested_options,
1334                           NULL, pb, iterpool));
1335       if (! opt)
1336         continue;
1337
1338       if (strcmp(opt->code, "q") == 0)
1339         {
1340           *option = svn_client_conflict_option_find_by_id(resolution_options,
1341                                                           opt->choice);
1342           *quit = TRUE;
1343           break;
1344         }
1345       else if (strcmp(opt->code, "dc") == 0)
1346         {
1347           SVN_ERR(show_prop_conflict(base_propval, my_propval, their_propval,
1348                                      merged_propval,
1349                                      pb->cancel_func, pb->cancel_baton,
1350                                      scratch_pool));
1351         }
1352       else if (strcmp(opt->code, "e") == 0)
1353         {
1354           SVN_ERR(edit_prop_conflict(&merged_propval,
1355                                      base_propval, my_propval, their_propval,
1356                                      editor_cmd, config, pb,
1357                                      result_pool, scratch_pool));
1358           resolved_allowed = (merged_propval != NULL);
1359         }
1360       else if (strcmp(opt->code, "r") == 0)
1361         {
1362           if (! resolved_allowed)
1363             {
1364               SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1365                              _("Invalid option; please edit the property "
1366                                "first.\n\n")));
1367               continue;
1368             }
1369
1370           *option = svn_client_conflict_option_find_by_id(
1371                       resolution_options,
1372                       svn_client_conflict_option_merged_text);
1373           svn_client_conflict_option_set_merged_propval(*option,
1374                                                         merged_propval);
1375           break;
1376         }
1377       else if (opt->choice != svn_client_conflict_option_undefined)
1378         {
1379           *option = svn_client_conflict_option_find_by_id(resolution_options,
1380                                                           opt->choice);
1381           break;
1382         }
1383     }
1384   svn_pool_destroy(iterpool);
1385
1386   return SVN_NO_ERROR;
1387 }
1388
1389 /* Ask the user what to do about the property conflicts described by CONFLICT
1390  * and either resolve them accordingly or postpone resolution.
1391  * SCRATCH_POOL is used for temporary allocations. */
1392 static svn_error_t *
1393 handle_prop_conflicts(svn_boolean_t *resolved,
1394                       svn_boolean_t *postponed,
1395                       svn_boolean_t *quit,
1396                       const svn_string_t **merged_value,
1397                       const char *path_prefix,
1398                       svn_cmdline_prompt_baton_t *pb,
1399                       const char *editor_cmd,
1400                       apr_hash_t *config,
1401                       svn_client_conflict_t *conflict,
1402                       svn_cl__conflict_stats_t *conflict_stats,
1403                       svn_client_ctx_t *ctx,
1404                       apr_pool_t *result_pool,
1405                       apr_pool_t *scratch_pool)
1406 {
1407   apr_array_header_t *props_conflicted;
1408   apr_pool_t *iterpool;
1409   int i;
1410   int nresolved = 0;
1411
1412   SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
1413                                              conflict, scratch_pool,
1414                                              scratch_pool));
1415
1416   iterpool = svn_pool_create(scratch_pool);
1417   for (i = 0; i < props_conflicted->nelts; i++)
1418     {
1419       const char *propname = APR_ARRAY_IDX(props_conflicted, i, const char *);
1420       svn_client_conflict_option_t *option;
1421       svn_client_conflict_option_id_t option_id;
1422
1423       svn_pool_clear(iterpool);
1424
1425       SVN_ERR(handle_one_prop_conflict(&option, quit, path_prefix, pb,
1426                                        editor_cmd, config, conflict, propname,
1427                                        ctx,
1428                                        iterpool, iterpool));
1429       option_id = svn_client_conflict_option_get_id(option);
1430
1431       if (option_id != svn_client_conflict_option_unspecified &&
1432           option_id != svn_client_conflict_option_postpone)
1433         {
1434           const char *local_relpath =
1435             svn_cl__local_style_skip_ancestor(
1436               path_prefix, svn_client_conflict_get_local_abspath(conflict),
1437               iterpool);
1438
1439           SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option,
1440                                                    ctx, iterpool));
1441           svn_cl__conflict_stats_resolved(conflict_stats, local_relpath,
1442                                           svn_wc_conflict_kind_property);
1443           nresolved++;
1444           *postponed = FALSE;
1445         }
1446       else
1447         *postponed = (option_id == svn_client_conflict_option_postpone);
1448
1449       if (*quit)
1450         break;
1451     }
1452   svn_pool_destroy(iterpool);
1453
1454   /* Indicate success if no property conflicts remain. */
1455   *resolved = (nresolved == props_conflicted->nelts);
1456
1457   return SVN_NO_ERROR;
1458 }
1459
1460 /* Set *OPTIONS to an array of resolution options for CONFLICT. */
1461 static svn_error_t *
1462 build_tree_conflict_options(
1463   apr_array_header_t **options,
1464   apr_array_header_t **possible_moved_to_repos_relpaths,
1465   apr_array_header_t **possible_moved_to_abspaths,
1466   svn_boolean_t *all_options_are_dumb,
1467   svn_client_conflict_t *conflict,
1468   svn_client_ctx_t *ctx,
1469   apr_pool_t *result_pool,
1470   apr_pool_t *scratch_pool)
1471 {
1472   const client_option_t *o;
1473   apr_array_header_t *builtin_options;
1474   int nopt;
1475   int i;
1476   int next_unknown_option_code = 1;
1477   apr_pool_t *iterpool;
1478
1479   if (all_options_are_dumb != NULL)
1480     *all_options_are_dumb = TRUE;
1481
1482   SVN_ERR(svn_client_conflict_tree_get_resolution_options(&builtin_options,
1483                                                           conflict, ctx,
1484                                                           scratch_pool,
1485                                                           scratch_pool));
1486   nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options_tree) +
1487            ARRAY_LEN(extra_resolver_options);
1488   *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *));
1489   *possible_moved_to_abspaths = NULL;
1490   *possible_moved_to_repos_relpaths = NULL;
1491
1492   iterpool = svn_pool_create(scratch_pool);
1493   for (i = 0; i < builtin_options->nelts; i++)
1494     {
1495       client_option_t *opt;
1496       svn_client_conflict_option_t *builtin_option;
1497       svn_client_conflict_option_id_t id;
1498
1499       svn_pool_clear(iterpool);
1500       builtin_option = APR_ARRAY_IDX(builtin_options, i,
1501                                      svn_client_conflict_option_t *);
1502       SVN_ERR(find_option_by_builtin(&opt, conflict,
1503                                      builtin_resolver_options,
1504                                      builtin_option,
1505                                      result_pool,
1506                                      iterpool));
1507       if (opt == NULL)
1508         {
1509           /* Unkown option. Assign a dynamic option code. */
1510           opt = apr_pcalloc(result_pool, sizeof(*opt));
1511           opt->code = apr_psprintf(result_pool, "%d", next_unknown_option_code);
1512           next_unknown_option_code++;
1513           opt->label = svn_client_conflict_option_get_label(builtin_option,
1514                                                             result_pool);
1515           opt->long_desc = svn_client_conflict_option_get_description(
1516                              builtin_option, result_pool);
1517           opt->choice = svn_client_conflict_option_get_id(builtin_option);
1518           opt->accept_arg = NULL;
1519         }
1520
1521       APR_ARRAY_PUSH(*options, client_option_t *) = opt;
1522
1523       id = svn_client_conflict_option_get_id(builtin_option);
1524
1525       /* Check if we got a "smart" tree conflict option. */
1526       if (all_options_are_dumb != NULL &&
1527           *all_options_are_dumb &&
1528           id != svn_client_conflict_option_postpone &&
1529           id != svn_client_conflict_option_accept_current_wc_state)
1530         *all_options_are_dumb = FALSE;
1531
1532       if (id == svn_client_conflict_option_incoming_move_file_text_merge ||
1533           id == svn_client_conflict_option_incoming_move_dir_merge)
1534         {
1535           SVN_ERR(
1536             svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
1537               possible_moved_to_repos_relpaths, builtin_option,
1538               result_pool, iterpool));
1539           SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates(
1540                     possible_moved_to_abspaths, builtin_option,
1541                     result_pool, iterpool));
1542         }
1543     }
1544
1545   svn_pool_destroy(iterpool);
1546
1547   for (o = extra_resolver_options_tree; o->code; o++)
1548     {
1549       /* Add move target choice options only if there are multiple
1550        * move targets to choose from. */
1551       if (strcmp(o->code, "d") == 0 &&
1552           (*possible_moved_to_repos_relpaths == NULL || 
1553            (*possible_moved_to_repos_relpaths)->nelts <= 1))
1554         continue;
1555       if (strcmp(o->code, "w") == 0 &&
1556           (*possible_moved_to_abspaths == NULL || 
1557            (*possible_moved_to_abspaths)->nelts <= 1))
1558         continue;
1559
1560       APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1561     }
1562   for (o = extra_resolver_options; o->code; o++)
1563     APR_ARRAY_PUSH(*options, const client_option_t *) = o;
1564
1565   return SVN_NO_ERROR;
1566 }
1567
1568 /* Make the user select a move target path for the moved-away VICTIM_ABSPATH. */
1569 static svn_error_t *
1570 prompt_move_target_path(int *preferred_move_target_idx,
1571                         apr_array_header_t *possible_moved_to_paths,
1572                         svn_boolean_t paths_are_local,
1573                         svn_cmdline_prompt_baton_t *pb,
1574                         const char *victim_abspath,
1575                         svn_client_ctx_t *ctx,
1576                         apr_pool_t *scratch_pool)
1577 {
1578   const char *move_targets_prompt = "";
1579   const char *move_targets_list = "";
1580   const char *wcroot_abspath;
1581   const char *victim_relpath;
1582   int i;
1583   apr_int64_t idx;
1584   apr_pool_t *iterpool;
1585
1586   SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, victim_abspath,
1587                                  ctx, scratch_pool, scratch_pool));
1588   victim_relpath = svn_cl__local_style_skip_ancestor(wcroot_abspath,
1589                                                      victim_abspath,
1590                                                      scratch_pool),
1591   iterpool = svn_pool_create(scratch_pool);
1592
1593   /* Build the prompt. */
1594   for (i = 0; i < possible_moved_to_paths->nelts; i++)
1595     {
1596       svn_pool_clear(iterpool);
1597
1598       if (paths_are_local)
1599         {
1600           const char *moved_to_abspath;
1601           const char *moved_to_relpath;
1602
1603           moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_paths, i,
1604                                            const char *);
1605           moved_to_relpath = svn_cl__local_style_skip_ancestor(
1606                                wcroot_abspath, moved_to_abspath, iterpool),
1607           move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '%s'\n",
1608                                            move_targets_list, i + 1,
1609                                            moved_to_relpath);
1610         }
1611       else
1612         {
1613           const char *moved_to_repos_relpath;
1614
1615           moved_to_repos_relpath = APR_ARRAY_IDX(possible_moved_to_paths, i,
1616                                                  const char *);
1617           move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '^/%s'\n",
1618                                            move_targets_list, i + 1,
1619                                            moved_to_repos_relpath);
1620         }
1621     }
1622   if (paths_are_local)
1623     move_targets_prompt =
1624       apr_psprintf(scratch_pool,
1625                    _("Possible working copy destinations for moved-away '%s' "
1626                      "are:\n%s"
1627                      "Only one destination can be a move; the others are "
1628                      "copies.\n"
1629                      "Specify the correct move target path by number: "),
1630                    victim_relpath, move_targets_list);
1631   else
1632     move_targets_prompt =
1633       apr_psprintf(scratch_pool,
1634                    _("Possible repository destinations for moved-away '%s' "
1635                      "are:\n%s"
1636                      "Only one destination can be a move; the others are "
1637                      "copies.\n"
1638                      "Specify the correct move target path by number: "),
1639                    victim_relpath, move_targets_list);
1640
1641   /* Keep asking the user until we got a valid choice. */
1642   while (1)
1643     {
1644       const char *answer;
1645       svn_error_t *err;
1646
1647       svn_pool_clear(iterpool);
1648
1649       SVN_ERR(svn_cmdline_prompt_user2(&answer, move_targets_prompt,
1650                                        pb, iterpool));
1651       err = svn_cstring_strtoi64(&idx, answer, 1,
1652                                  possible_moved_to_paths->nelts, 10);
1653       if (err)
1654         {
1655           char buf[1024];
1656
1657           svn_cmdline_fprintf(stderr, iterpool, "%s\n",
1658                               svn_err_best_message(err, buf, sizeof(buf)));
1659           svn_error_clear(err);
1660           continue;
1661         }
1662
1663       break;
1664     }
1665
1666   svn_pool_destroy(iterpool);
1667
1668   SVN_ERR_ASSERT((idx - 1) == (int)(idx - 1));
1669   *preferred_move_target_idx = (int)(idx - 1);
1670   return SVN_NO_ERROR;
1671 }
1672
1673 /* Ask the user what to do about the tree conflict described by CONFLICT
1674  * and either resolve the conflict accordingly or postpone resolution.
1675  * SCRATCH_POOL is used for temporary allocations. */
1676 static svn_error_t *
1677 handle_tree_conflict(svn_boolean_t *resolved,
1678                      svn_boolean_t *postponed,
1679                      svn_boolean_t *quit,
1680                      svn_boolean_t *printed_description,
1681                      svn_client_conflict_t *conflict,
1682                      const char *path_prefix,
1683                      svn_cmdline_prompt_baton_t *pb,
1684                      svn_cl__conflict_stats_t *conflict_stats,
1685                      svn_client_ctx_t *ctx,
1686                      apr_pool_t *scratch_pool)
1687 {
1688   apr_pool_t *iterpool;
1689   apr_array_header_t *tree_conflict_options;
1690   svn_client_conflict_option_id_t option_id;
1691   const char *local_abspath;
1692   const char *conflict_description;
1693   const char *local_change_description;
1694   const char *incoming_change_description;
1695   apr_array_header_t *possible_moved_to_repos_relpaths;
1696   apr_array_header_t *possible_moved_to_abspaths;
1697   svn_boolean_t all_options_are_dumb;
1698   const struct client_option_t *recommended_option;
1699   svn_boolean_t repos_move_target_chosen = FALSE;
1700   svn_boolean_t wc_move_target_chosen = FALSE;
1701
1702   option_id = svn_client_conflict_option_unspecified;
1703   local_abspath = svn_client_conflict_get_local_abspath(conflict);
1704
1705   /* Always show the best possible conflict description and options. */
1706   SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, scratch_pool));
1707
1708   SVN_ERR(svn_client_conflict_tree_get_description(
1709            &incoming_change_description, &local_change_description,
1710            conflict, ctx, scratch_pool, scratch_pool));
1711   conflict_description = apr_psprintf(scratch_pool, "%s\n%s",
1712                                       incoming_change_description,
1713                                       local_change_description);
1714   if (!*printed_description)
1715     SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1716                                 _("Tree conflict on '%s':\n%s\n"),
1717                                 svn_cl__local_style_skip_ancestor(
1718                                   path_prefix, local_abspath, scratch_pool),
1719                                 conflict_description));
1720
1721   SVN_ERR(build_tree_conflict_options(&tree_conflict_options,
1722                                       &possible_moved_to_repos_relpaths,
1723                                       &possible_moved_to_abspaths,
1724                                       &all_options_are_dumb,
1725                                       conflict, ctx,
1726                                       scratch_pool, scratch_pool));
1727
1728   /* Try a recommended resolution option before prompting. */
1729   recommended_option = find_recommended_option(tree_conflict_options);
1730   if (recommended_option)
1731     {
1732       svn_error_t *err;
1733       apr_status_t root_cause;
1734
1735       SVN_ERR(svn_cmdline_printf(scratch_pool,
1736                                  _("Applying recommended resolution '%s':\n"),
1737                                  recommended_option->label));
1738
1739       err = mark_conflict_resolved(conflict, recommended_option->choice,
1740                                    FALSE, NULL, TRUE,
1741                                    path_prefix, conflict_stats,
1742                                    ctx, scratch_pool);
1743       if (!err)
1744         {
1745           *resolved = TRUE;
1746           return SVN_NO_ERROR;
1747         }
1748
1749       root_cause = svn_error_root_cause(err)->apr_err;
1750       if (root_cause != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE &&
1751           root_cause != SVN_ERR_WC_OBSTRUCTED_UPDATE &&
1752           root_cause != SVN_ERR_WC_FOUND_CONFLICT)
1753         return svn_error_trace(err);
1754
1755       /* Fall back to interactive prompting. */
1756       svn_error_clear(err);
1757     }
1758
1759   if (all_options_are_dumb)
1760     SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1761                                 _("\nSubversion is not smart enough to resolve "
1762                                   "this tree conflict automatically!\nSee 'svn "
1763                                   "help resolve' for more information.\n\n")));
1764
1765   iterpool = svn_pool_create(scratch_pool);
1766   while (1)
1767     {
1768       const client_option_t *opt;
1769
1770       svn_pool_clear(iterpool);
1771
1772       if (!repos_move_target_chosen &&
1773           possible_moved_to_repos_relpaths &&
1774           possible_moved_to_repos_relpaths->nelts > 1)
1775         SVN_ERR(svn_cmdline_printf(scratch_pool,
1776                   _("Ambiguous move destinations exist in the repository; "
1777                     "try the 'd' option\n")));
1778       if (!wc_move_target_chosen && possible_moved_to_abspaths &&
1779           possible_moved_to_abspaths->nelts > 1)
1780         SVN_ERR(svn_cmdline_printf(scratch_pool,
1781                   _("Ambiguous move destinations exist in the working copy; "
1782                     "try the 'w' option\n")));
1783
1784       SVN_ERR(prompt_user(&opt, tree_conflict_options, NULL,
1785                           conflict_description, pb, iterpool));
1786       *printed_description = TRUE;
1787       if (! opt)
1788         continue;
1789
1790       if (strcmp(opt->code, "q") == 0)
1791         {
1792           option_id = opt->choice;
1793           *quit = TRUE;
1794           break;
1795         }
1796       else if (strcmp(opt->code, "d") == 0)
1797         {
1798           int preferred_move_target_idx;
1799           apr_array_header_t *options;
1800           svn_client_conflict_option_t *conflict_option;
1801
1802           SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,
1803                                           possible_moved_to_repos_relpaths,
1804                                           FALSE,
1805                                           pb, local_abspath, ctx, iterpool));
1806
1807           /* Update preferred move target path. */
1808           SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options,
1809                                                                   conflict,
1810                                                                   ctx,
1811                                                                   iterpool,
1812                                                                   iterpool));
1813           conflict_option =
1814             svn_client_conflict_option_find_by_id( 
1815               options,
1816               svn_client_conflict_option_incoming_move_file_text_merge);
1817           if (conflict_option == NULL)
1818             {
1819               conflict_option =
1820                 svn_client_conflict_option_find_by_id( 
1821                   options, svn_client_conflict_option_incoming_move_dir_merge);
1822             }
1823
1824           if (conflict_option)
1825             {
1826               SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(
1827                         conflict_option, preferred_move_target_idx,
1828                         ctx, iterpool));
1829               repos_move_target_chosen = TRUE;
1830               wc_move_target_chosen = FALSE;
1831
1832               /* Update option description. */
1833               SVN_ERR(build_tree_conflict_options(
1834                         &tree_conflict_options,
1835                         &possible_moved_to_repos_relpaths,
1836                         &possible_moved_to_abspaths,
1837                         NULL, conflict, ctx,
1838                         scratch_pool, scratch_pool));
1839
1840               /* Update conflict description. */
1841               SVN_ERR(svn_client_conflict_tree_get_description(
1842                        &incoming_change_description, &local_change_description,
1843                        conflict, ctx, scratch_pool, scratch_pool));
1844               conflict_description = apr_psprintf(scratch_pool, "%s\n%s",
1845                                                   incoming_change_description,
1846                                                   local_change_description);
1847             }
1848           continue;
1849         }
1850       else if (strcmp(opt->code, "w") == 0)
1851         {
1852           int preferred_move_target_idx;
1853           apr_array_header_t *options;
1854           svn_client_conflict_option_t *conflict_option;
1855
1856           SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,
1857                                            possible_moved_to_abspaths, TRUE,
1858                                            pb, local_abspath, ctx, iterpool));
1859
1860           /* Update preferred move target path. */
1861           SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options,
1862                                                                   conflict,
1863                                                                   ctx,
1864                                                                   iterpool,
1865                                                                   iterpool));
1866           conflict_option =
1867             svn_client_conflict_option_find_by_id( 
1868               options,
1869               svn_client_conflict_option_incoming_move_file_text_merge);
1870           if (conflict_option == NULL)
1871             {
1872               conflict_option =
1873                 svn_client_conflict_option_find_by_id( 
1874                   options, svn_client_conflict_option_incoming_move_dir_merge);
1875             }
1876
1877           if (conflict_option)
1878             {
1879               SVN_ERR(svn_client_conflict_option_set_moved_to_abspath(
1880                         conflict_option, preferred_move_target_idx, ctx,
1881                         iterpool));
1882               wc_move_target_chosen = TRUE;
1883
1884               /* Update option description. */
1885               SVN_ERR(build_tree_conflict_options(
1886                         &tree_conflict_options,
1887                         &possible_moved_to_repos_relpaths,
1888                         &possible_moved_to_abspaths,
1889                         NULL, conflict, ctx,
1890                         scratch_pool, scratch_pool));
1891             }
1892           continue;
1893         }
1894       else if (opt->choice != svn_client_conflict_option_undefined)
1895         {
1896           option_id = opt->choice;
1897           break;
1898         }
1899     }
1900   svn_pool_destroy(iterpool);
1901   if (option_id != svn_client_conflict_option_unspecified &&
1902       option_id != svn_client_conflict_option_postpone)
1903     {
1904       SVN_ERR(mark_conflict_resolved(conflict, option_id,
1905                                      FALSE, NULL, TRUE,
1906                                      path_prefix, conflict_stats,
1907                                      ctx, scratch_pool));
1908       *resolved = TRUE;
1909     }
1910   else
1911     {
1912       *resolved = FALSE;
1913       *postponed = (option_id == svn_client_conflict_option_postpone);
1914     }
1915
1916   return SVN_NO_ERROR;
1917 }
1918
1919 static svn_error_t *
1920 resolve_conflict_interactively(svn_boolean_t *resolved,
1921                                svn_boolean_t *postponed,
1922                                svn_boolean_t *quit,
1923                                svn_boolean_t *external_failed,
1924                                svn_boolean_t *printed_summary,
1925                                svn_boolean_t *printed_description,
1926                                svn_client_conflict_t *conflict,
1927                                const char *editor_cmd,
1928                                apr_hash_t *config,
1929                                const char *path_prefix,
1930                                svn_cmdline_prompt_baton_t *pb,
1931                                svn_cl__conflict_stats_t *conflict_stats,
1932                                svn_client_ctx_t *ctx,
1933                                apr_pool_t *result_pool,
1934                                apr_pool_t *scratch_pool)
1935 {
1936   svn_boolean_t text_conflicted;
1937   apr_array_header_t *props_conflicted;
1938   svn_boolean_t tree_conflicted;
1939   const svn_string_t *merged_propval;
1940
1941   SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
1942                                              &props_conflicted,
1943                                              &tree_conflicted,
1944                                              conflict,
1945                                              scratch_pool,
1946                                              scratch_pool));
1947
1948   /* Print a summary of conflicts before starting interactive resolution */
1949   if (! *printed_summary)
1950     {
1951       SVN_ERR(svn_cl__print_conflict_stats(conflict_stats, scratch_pool));
1952       *printed_summary = TRUE;
1953     }
1954
1955   *resolved = FALSE;
1956   if (text_conflicted
1957        && (svn_client_conflict_get_incoming_change(conflict) ==
1958            svn_wc_conflict_action_edit)
1959        && (svn_client_conflict_get_local_change(conflict) ==
1960            svn_wc_conflict_reason_edited))
1961     SVN_ERR(handle_text_conflict(resolved, postponed, quit, printed_description,
1962                                  conflict, path_prefix, pb, editor_cmd, config,
1963                                  conflict_stats, ctx, scratch_pool));
1964   if (props_conflicted->nelts > 0)
1965     SVN_ERR(handle_prop_conflicts(resolved, postponed, quit, &merged_propval,
1966                                   path_prefix, pb, editor_cmd, config, conflict,
1967                                   conflict_stats, ctx, result_pool, scratch_pool));
1968   if (tree_conflicted)
1969     SVN_ERR(handle_tree_conflict(resolved, postponed, quit, printed_description,
1970                                  conflict, path_prefix, pb, conflict_stats, ctx,
1971                                  scratch_pool));
1972
1973   return SVN_NO_ERROR;
1974 }
1975
1976 svn_error_t *
1977 svn_cl__resolve_conflict(svn_boolean_t *quit,
1978                          svn_boolean_t *external_failed,
1979                          svn_boolean_t *printed_summary,
1980                          svn_client_conflict_t *conflict,
1981                          svn_cl__accept_t accept_which,
1982                          const char *editor_cmd,
1983                          const char *path_prefix,
1984                          svn_cmdline_prompt_baton_t *pb,
1985                          svn_cl__conflict_stats_t *conflict_stats,
1986                          svn_client_ctx_t *ctx,
1987                          apr_pool_t *scratch_pool)
1988 {
1989   svn_boolean_t text_conflicted;
1990   apr_array_header_t *props_conflicted;
1991   svn_boolean_t tree_conflicted;
1992   const char *local_abspath;
1993   svn_client_conflict_option_id_t option_id;
1994
1995   SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted,
1996                                              &props_conflicted,
1997                                              &tree_conflicted,
1998                                              conflict,
1999                                              scratch_pool,
2000                                              scratch_pool));
2001   local_abspath = svn_client_conflict_get_local_abspath(conflict);
2002
2003   if (accept_which == svn_cl__accept_unspecified)
2004     {
2005       option_id = svn_client_conflict_option_unspecified;
2006     }
2007   else if (accept_which == svn_cl__accept_postpone)
2008     {
2009       option_id = svn_client_conflict_option_postpone;
2010     }
2011   else if (accept_which == svn_cl__accept_base)
2012     {
2013       option_id = svn_client_conflict_option_base_text;
2014     }
2015   else if (accept_which == svn_cl__accept_working)
2016     {
2017       option_id = svn_client_conflict_option_merged_text;
2018
2019       if (text_conflicted)
2020         {
2021           const char *mime_type =
2022             svn_client_conflict_text_get_mime_type(conflict);
2023
2024           /* There is no merged text for binary conflicts, behave as
2025            * if 'mine-full' was chosen. */
2026           if (mime_type && svn_mime_type_is_binary(mime_type))
2027             option_id = svn_client_conflict_option_working_text;
2028         }
2029       else if (tree_conflicted)
2030         {
2031           /* For tree conflicts, map 'working' to 'accept current working
2032            * copy state'. */
2033           option_id = svn_client_conflict_option_accept_current_wc_state;
2034         }
2035     }
2036   else if (accept_which == svn_cl__accept_theirs_conflict)
2037     {
2038       option_id = svn_client_conflict_option_incoming_text_where_conflicted;
2039     }
2040   else if (accept_which == svn_cl__accept_mine_conflict)
2041     {
2042       option_id = svn_client_conflict_option_working_text_where_conflicted;
2043
2044       if (tree_conflicted)
2045         {
2046           svn_wc_operation_t operation;
2047
2048           operation = svn_client_conflict_get_operation(conflict);
2049           if (operation == svn_wc_operation_update ||
2050               operation == svn_wc_operation_switch)
2051             {
2052               svn_wc_conflict_reason_t reason;
2053
2054               reason = svn_client_conflict_get_local_change(conflict);
2055               if (reason == svn_wc_conflict_reason_moved_away)
2056                 {
2057                   /* Map 'mine-conflict' to 'update move destination'. */
2058                   option_id =
2059                     svn_client_conflict_option_update_move_destination;
2060                 }
2061               else if (reason == svn_wc_conflict_reason_deleted ||
2062                        reason == svn_wc_conflict_reason_replaced)
2063                 {
2064                   svn_wc_conflict_action_t action;
2065                   svn_node_kind_t node_kind;
2066
2067                   action = svn_client_conflict_get_incoming_change(conflict);
2068                   node_kind =
2069                     svn_client_conflict_tree_get_victim_node_kind(conflict);
2070
2071                   if (action == svn_wc_conflict_action_edit &&
2072                       node_kind == svn_node_dir)
2073                     {
2074                       /* Map 'mine-conflict' to 'update any moved away
2075                        * children'. */
2076                       option_id =
2077                         svn_client_conflict_option_update_any_moved_away_children;
2078                     }
2079                 }
2080             }
2081         }
2082     }
2083   else if (accept_which == svn_cl__accept_theirs_full)
2084     {
2085       option_id = svn_client_conflict_option_incoming_text;
2086     }
2087   else if (accept_which == svn_cl__accept_mine_full)
2088     {
2089       option_id = svn_client_conflict_option_working_text;
2090     }
2091   else if (accept_which == svn_cl__accept_edit)
2092     {
2093       option_id = svn_client_conflict_option_unspecified;
2094
2095       if (local_abspath)
2096         {
2097           if (*external_failed)
2098             {
2099               option_id = svn_client_conflict_option_postpone;
2100             }
2101           else
2102             {
2103               svn_error_t *err;
2104
2105               err = svn_cmdline__edit_file_externally(local_abspath,
2106                                                       editor_cmd,
2107                                                       ctx->config,
2108                                                       scratch_pool);
2109               if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR ||
2110                           err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
2111                 {
2112                   char buf[1024];
2113                   const char *message;
2114
2115                   message = svn_err_best_message(err, buf, sizeof(buf));
2116                   SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
2117                                               message));
2118                   svn_error_clear(err);
2119                   *external_failed = TRUE;
2120                 }
2121               else if (err)
2122                 return svn_error_trace(err);
2123               option_id = svn_client_conflict_option_merged_text;
2124             }
2125         }
2126     }
2127   else if (accept_which == svn_cl__accept_launch)
2128     {
2129       const char *base_abspath = NULL;
2130       const char *my_abspath = NULL;
2131       const char *their_abspath = NULL;
2132
2133       option_id = svn_client_conflict_option_unspecified;
2134
2135       if (text_conflicted)
2136         SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath,
2137                                                       &base_abspath,
2138                                                       &their_abspath,
2139                                                       conflict, scratch_pool,
2140                                                       scratch_pool));
2141
2142       if (base_abspath && their_abspath && my_abspath && local_abspath)
2143         {
2144           if (*external_failed)
2145             {
2146               option_id = svn_client_conflict_option_postpone;
2147             }
2148           else
2149             {
2150               svn_boolean_t remains_in_conflict;
2151               svn_error_t *err;
2152
2153               err = svn_cl__merge_file_externally(base_abspath, their_abspath,
2154                                                   my_abspath, local_abspath,
2155                                                   local_abspath, ctx->config,
2156                                                   &remains_in_conflict,
2157                                                   scratch_pool);
2158               if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL ||
2159                           err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
2160                 {
2161                   char buf[1024];
2162                   const char *message;
2163
2164                   message = svn_err_best_message(err, buf, sizeof(buf));
2165                   SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
2166                                               message));
2167                   *external_failed = TRUE;
2168                   return svn_error_trace(err);
2169                 }
2170               else if (err)
2171                 return svn_error_trace(err);
2172
2173               if (remains_in_conflict)
2174                 option_id = svn_client_conflict_option_postpone;
2175               else
2176                 option_id = svn_client_conflict_option_merged_text;
2177             }
2178         }
2179     }
2180   else if (accept_which == svn_cl__accept_recommended)
2181     {
2182       svn_client_conflict_option_id_t recommended_id;
2183
2184       if (tree_conflicted)
2185         SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx,
2186                                                      scratch_pool));
2187       recommended_id = svn_client_conflict_get_recommended_option_id(conflict);
2188       if (recommended_id != svn_client_conflict_option_unspecified)
2189         option_id = recommended_id;
2190       else
2191         option_id = svn_client_conflict_option_postpone;
2192     }
2193   else
2194     SVN_ERR_MALFUNCTION();
2195
2196   /* If we are in interactive mode and either the user gave no --accept
2197    * option or the option did not apply, then prompt. */
2198   if (option_id == svn_client_conflict_option_unspecified)
2199     {
2200       svn_boolean_t resolved = FALSE;
2201       svn_boolean_t postponed = FALSE;
2202       svn_boolean_t printed_description = FALSE;
2203       svn_error_t *err;
2204
2205       *quit = FALSE;
2206
2207       while (!resolved && !postponed && !*quit)
2208         {
2209           err = resolve_conflict_interactively(&resolved, &postponed, quit,
2210                                                external_failed,
2211                                                printed_summary,
2212                                                &printed_description,
2213                                                conflict,
2214                                                editor_cmd, ctx->config,
2215                                                path_prefix, pb,
2216                                                conflict_stats, ctx,
2217                                                scratch_pool, scratch_pool);
2218           if (err && err->apr_err == SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE)
2219             {
2220               /* Conflict resolution has failed. Let the user try again.
2221                * It is always possible to break out of this loop with
2222                * the 'quit' or 'postpone' options. */
2223               svn_handle_warning2(stderr, err, "svn: ");
2224               svn_error_clear(err);
2225               err = SVN_NO_ERROR;
2226             }
2227           SVN_ERR(err);
2228         }
2229     }
2230   else if (option_id != svn_client_conflict_option_postpone)
2231     SVN_ERR(mark_conflict_resolved(conflict, option_id,
2232                                    text_conflicted,
2233                                    props_conflicted->nelts > 0 ? "" : NULL,
2234                                    tree_conflicted,
2235                                    path_prefix, conflict_stats,
2236                                    ctx, scratch_pool));
2237
2238   return SVN_NO_ERROR;
2239 }