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