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