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