Skip some mergeinfo-related tests in merge_authz, switch, and update
[svn.git] / subversion / svn / conflict-callbacks.c
blobb2a9193aeae5c78248ddd99729b6095394dd7395
1 /*
2 * conflict-callbacks.c: conflict resolution callbacks specific to the
3 * commandline client.
5 * ====================================================================
6 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
8 * This software is licensed as described in the file COPYING, which
9 * you should have received as part of this distribution. The terms
10 * are also available at http://subversion.tigris.org/license-1.html.
11 * If newer versions of this license are posted there, you may use a
12 * newer version instead, at your option.
14 * This software consists of voluntary contributions made by many
15 * individuals. For exact contribution history, see the revision
16 * history and logs, available at http://subversion.tigris.org/.
17 * ====================================================================
20 /* ==================================================================== */
24 /*** Includes. ***/
26 #define APR_WANT_STDIO
27 #define APR_WANT_STRFUNC
28 #include <apr_want.h>
30 #include "svn_cmdline.h"
31 #include "svn_client.h"
32 #include "svn_types.h"
33 #include "svn_pools.h"
34 #include "cl.h"
36 #include "svn_private_config.h"
41 svn_cl__conflict_baton_t *
42 svn_cl__conflict_baton_make(svn_cl__accept_t accept_which,
43 apr_hash_t *config,
44 const char *editor_cmd,
45 svn_cmdline_prompt_baton_t *pb,
46 apr_pool_t *pool)
48 svn_cl__conflict_baton_t *b = apr_palloc(pool, sizeof(*b));
49 b->accept_which = accept_which;
50 b->config = config;
51 b->editor_cmd = editor_cmd;
52 b->external_failed = FALSE;
53 b->pb = pb;
54 return b;
57 svn_cl__accept_t
58 svn_cl__accept_from_word(const char *word)
60 if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0)
61 return svn_cl__accept_postpone;
62 if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0)
63 return svn_cl__accept_base;
64 if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0)
65 return svn_cl__accept_working;
66 #if 0 /* not yet implemented */
67 if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0)
68 return svn_cl__accept_mine_conflict;
69 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0)
70 return svn_cl__accept_theirs_conflict;
71 #endif /* 0 */
72 if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0)
73 return svn_cl__accept_mine_full;
74 if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0)
75 return svn_cl__accept_theirs_full;
76 if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0)
77 return svn_cl__accept_edit;
78 if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0)
79 return svn_cl__accept_launch;
80 /* word is an invalid action. */
81 return svn_cl__accept_invalid;
85 static svn_error_t *
86 show_diff(svn_boolean_t *performed_edit,
87 const svn_wc_conflict_description_t *desc,
88 apr_pool_t *pool)
90 const char *path1, *path2;
91 svn_diff_t *diff;
92 svn_stream_t *output;
93 svn_diff_file_options_t *options;
95 if (desc->merged_file && desc->base_file)
97 /* Show the conflict markers to the user */
98 path1 = desc->base_file;
99 path2 = desc->merged_file;
101 else
103 /* There's no base file, but we can show the
104 difference between mine and theirs. */
105 path1 = desc->their_file;
106 path2 = desc->my_file;
109 options = svn_diff_file_options_create(pool);
110 options->ignore_eol_style = TRUE;
111 SVN_ERR(svn_stream_for_stdout(&output, pool));
112 SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2,
113 options, pool));
114 SVN_ERR(svn_diff_file_output_unified2(output, diff,
115 path1, path2,
116 NULL, NULL,
117 APR_LOCALE_CHARSET,
118 pool));
120 *performed_edit = TRUE;
122 return SVN_NO_ERROR;
126 static svn_error_t *
127 open_editor(svn_boolean_t *performed_edit,
128 const svn_wc_conflict_description_t *desc,
129 svn_cl__conflict_baton_t *b,
130 apr_pool_t *pool)
132 svn_error_t *err;
134 if (desc->merged_file)
136 err = svn_cl__edit_file_externally(desc->merged_file, b->editor_cmd,
137 b->config, pool);
138 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
140 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
141 err->message ? err->message :
142 _("No editor found.")));
143 svn_error_clear(err);
145 else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
147 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
148 err->message ? err->message :
149 _("Error running editor.")));
150 svn_error_clear(err);
152 else if (err)
153 return err;
154 else
155 *performed_edit = TRUE;
157 else
158 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
159 _("Invalid option; there's no "
160 "merged version to edit.\n\n")));
162 return SVN_NO_ERROR;
166 static svn_error_t *
167 launch_resolver(svn_boolean_t *performed_edit,
168 const svn_wc_conflict_description_t *desc,
169 svn_cl__conflict_baton_t *b,
170 apr_pool_t *pool)
172 svn_error_t *err;
174 err = svn_cl__merge_file_externally(desc->base_file, desc->their_file,
175 desc->my_file, desc->merged_file,
176 b->config, pool);
177 if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
179 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
180 err->message ? err->message :
181 _("No merge tool found.\n")));
182 svn_error_clear(err);
184 else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
186 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
187 err->message ? err->message :
188 _("Error running merge tool.")));
189 svn_error_clear(err);
191 else if (err)
192 return err;
193 else if (performed_edit)
194 *performed_edit = TRUE;
196 return SVN_NO_ERROR;
200 /* Implement svn_wc_conflict_resolver_func_t; resolves based on
201 --accept option if given, else by prompting. */
202 svn_error_t *
203 svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
204 const svn_wc_conflict_description_t *desc,
205 void *baton,
206 apr_pool_t *pool)
208 svn_cl__conflict_baton_t *b = baton;
209 svn_error_t *err;
210 apr_pool_t *subpool;
212 /* Start out assuming we're going to postpone the conflict. */
213 *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
214 NULL, pool);
216 switch (b->accept_which)
218 case svn_cl__accept_invalid:
219 case svn_cl__accept_unspecified:
220 /* No (or no valid) --accept option, fall through to prompting. */
221 break;
222 case svn_cl__accept_postpone:
223 (*result)->choice = svn_wc_conflict_choose_postpone;
224 return SVN_NO_ERROR;
225 case svn_cl__accept_base:
226 (*result)->choice = svn_wc_conflict_choose_base;
227 return SVN_NO_ERROR;
228 case svn_cl__accept_working:
229 (*result)->choice = svn_wc_conflict_choose_merged;
230 return SVN_NO_ERROR;
231 case svn_cl__accept_mine_conflict:
232 (*result)->choice = svn_wc_conflict_choose_mine_conflict;
233 return SVN_NO_ERROR;
234 case svn_cl__accept_theirs_conflict:
235 (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
236 return SVN_NO_ERROR;
237 case svn_cl__accept_mine_full:
238 (*result)->choice = svn_wc_conflict_choose_mine_full;
239 return SVN_NO_ERROR;
240 case svn_cl__accept_theirs_full:
241 (*result)->choice = svn_wc_conflict_choose_theirs_full;
242 return SVN_NO_ERROR;
243 case svn_cl__accept_edit:
244 if (desc->merged_file)
246 if (b->external_failed)
248 (*result)->choice = svn_wc_conflict_choose_postpone;
249 return SVN_NO_ERROR;
252 err = svn_cl__edit_file_externally(desc->merged_file,
253 b->editor_cmd, b->config, pool);
254 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
256 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
257 err->message ? err->message :
258 _("No editor found,"
259 " leaving all conflicts.")));
260 svn_error_clear(err);
261 b->external_failed = TRUE;
263 else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
265 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
266 err->message ? err->message :
267 _("Error running editor,"
268 " leaving all conflicts.")));
269 svn_error_clear(err);
270 b->external_failed = TRUE;
272 else if (err)
273 return err;
274 (*result)->choice = svn_wc_conflict_choose_merged;
275 return SVN_NO_ERROR;
277 /* else, fall through to prompting. */
278 break;
279 case svn_cl__accept_launch:
280 if (desc->base_file && desc->their_file
281 && desc->my_file && desc->merged_file)
283 if (b->external_failed)
285 (*result)->choice = svn_wc_conflict_choose_postpone;
286 return SVN_NO_ERROR;
289 err = svn_cl__merge_file_externally(desc->base_file,
290 desc->their_file,
291 desc->my_file,
292 desc->merged_file,
293 b->config,
294 pool);
295 if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
297 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
298 err->message ? err->message :
299 _("No merge tool found,"
300 " leaving all conflicts.")));
301 svn_error_clear(err);
302 b->external_failed = TRUE;
304 else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
306 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
307 err->message ? err->message :
308 _("Error running merge tool"
309 " leaving all conflicts.")));
310 svn_error_clear(err);
311 b->external_failed = TRUE;
313 else if (err)
314 return err;
316 (*result)->choice = svn_wc_conflict_choose_merged;
317 return SVN_NO_ERROR;
319 /* else, fall through to prompting. */
320 break;
323 /* We're in interactive mode and either the user gave no --accept
324 option or the option did not apply; let's prompt. */
325 subpool = svn_pool_create(pool);
327 /* Handle the most common cases, which is either:
329 Conflicting edits on a file's text, or
330 Conflicting edits on a property.
332 if (((desc->node_kind == svn_node_file)
333 && (desc->action == svn_wc_conflict_action_edit)
334 && (desc->reason == svn_wc_conflict_reason_edited))
335 || (desc->kind == svn_wc_conflict_kind_property))
337 const char *answer;
338 char *prompt;
339 svn_boolean_t diff_allowed = FALSE;
340 svn_boolean_t performed_edit = FALSE;
342 if (desc->kind == svn_wc_conflict_kind_text)
343 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
344 _("Conflict discovered in '%s'.\n"),
345 desc->path));
346 else if (desc->kind == svn_wc_conflict_kind_property)
348 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
349 _("Conflict for property '%s' discovered"
350 " on '%s'.\n"),
351 desc->property_name, desc->path));
353 if ((!desc->my_file && desc->their_file)
354 || (desc->my_file && !desc->their_file))
356 /* One agent wants to change the property, one wants to
357 delete it. This is not something we can diff, so we
358 just tell the user. */
359 svn_stringbuf_t *myval = NULL, *theirval = NULL;
361 if (desc->my_file)
363 SVN_ERR(svn_stringbuf_from_file(&myval, desc->my_file,
364 subpool));
365 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
366 _("They want to delete the property, "
367 "you want to change the value to '%s'.\n"),
368 myval->data));
370 else
372 SVN_ERR(svn_stringbuf_from_file(&theirval, desc->their_file,
373 subpool));
374 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
375 _("They want to change the property value to '%s', "
376 "you want to delete the property.\n"),
377 theirval->data));
381 else
382 /* We don't recognize any other sort of conflict yet */
383 return SVN_NO_ERROR;
385 /* Diffing can happen between base and merged, to show conflict
386 markers to the user (this is the typical 3-way merge
387 scenario), or if no base is available, we can show a diff
388 between mine and theirs. */
389 if ((desc->merged_file && desc->base_file)
390 || (!desc->base_file && desc->my_file && desc->their_file))
391 diff_allowed = TRUE;
393 while (TRUE)
395 svn_pool_clear(subpool);
397 prompt = apr_pstrdup(subpool, _("Select: (p) postpone"));
398 if (diff_allowed)
399 prompt = apr_pstrcat(subpool, prompt,
400 _(", (df) diff-full, (e) edit"),
401 NULL);
402 else
403 prompt = apr_pstrcat(subpool, prompt,
404 _(", (mf) mine-full, (tf) theirs-full"),
405 NULL);
406 if (performed_edit)
407 prompt = apr_pstrcat(subpool, prompt, _(", (r) resolved"), NULL);
409 prompt = apr_pstrcat(subpool, prompt, ",\n ", NULL);
410 prompt = apr_pstrcat(subpool, prompt,
411 _("(s) show all options: "),
412 NULL);
414 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool));
416 if (strcmp(answer, "s") == 0)
418 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
419 _(" (p) postpone - mark the conflict to be "
420 "resolved later\n"
421 " (df) diff-full - show all changes made to merged file\n"
422 " (e) edit - change merged file in an editor\n"
423 " (r) resolved - accept merged version of file\n"
424 " (mf) mine-full - accept my version of entire file "
425 "(ignore their changes)\n"
426 " (tf) theirs-full - accept their version of entire file "
427 "(lose my changes)\n"
428 " (l) launch - launch external tool to "
429 "resolve conflict\n"
430 " (s) show all - show this list\n\n")));
432 else if (strcmp(answer, "p") == 0)
434 /* Do nothing, let file be marked conflicted. */
435 (*result)->choice = svn_wc_conflict_choose_postpone;
436 break;
438 else if (strcmp(answer, "mc") == 0)
440 SVN_ERR(svn_cmdline_fprintf
441 (stderr, subpool,
442 _("Sorry, '(mc) mine for conflicts' "
443 "is not yet implemented; see\n"
444 "http://subversion.tigris.org/issues/show_bug.cgi?id=3049\n\n")));
445 continue;
447 else if (strcmp(answer, "tc") == 0)
449 SVN_ERR(svn_cmdline_fprintf
450 (stderr, subpool,
451 _("Sorry, '(tc) theirs for conflicts' "
452 "is not yet implemented; see\n"
453 "http://subversion.tigris.org/issues/show_bug.cgi?id=3049\n\n")));
454 continue;
456 else if (strcmp(answer, "mf") == 0)
458 (*result)->choice = svn_wc_conflict_choose_mine_full;
459 break;
461 else if (strcmp(answer, "tf") == 0)
463 (*result)->choice = svn_wc_conflict_choose_theirs_full;
464 break;
466 else if (strcmp(answer, "dc") == 0)
468 SVN_ERR(svn_cmdline_fprintf
469 (stderr, subpool,
470 _("Sorry, '(dc) diff of conflicts' "
471 "is not yet implemented; see\n"
472 "http://subversion.tigris.org/issues/show_bug.cgi?id=3048\n\n")));
473 continue;
475 else if (strcmp(answer, "df") == 0)
477 if (! diff_allowed)
479 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
480 _("Invalid option; there's no "
481 "merged version to diff.\n\n")));
482 continue;
485 SVN_ERR(show_diff(&performed_edit, desc, subpool));
487 else if (strcmp(answer, "e") == 0)
489 SVN_ERR(open_editor(&performed_edit, desc, b, subpool));
491 else if (strcmp(answer, "l") == 0)
493 if (desc->base_file && desc->their_file && desc->my_file
494 && desc->merged_file)
495 SVN_ERR(launch_resolver(&performed_edit, desc, b, subpool));
496 else
497 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
498 _("Invalid option.\n\n")));
500 else if (strcmp(answer, "r") == 0)
502 /* We only allow the user accept the merged version of
503 the file if they've edited it, or at least looked at
504 the diff. */
505 if (performed_edit)
507 (*result)->choice = svn_wc_conflict_choose_merged;
508 break;
510 else
511 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
512 _("Invalid option.\n\n")));
517 Dealing with obstruction of additions can be tricky. The
518 obstructing item could be unversioned, versioned, or even
519 schedule-add. Here's a matrix of how the caller should behave,
520 based on results we return.
522 Unversioned Versioned Schedule-Add
524 choose_mine skip addition, skip addition skip addition
525 add existing item
527 choose_theirs destroy file, schedule-delete, revert add,
528 add new item. add new item. rm file,
529 add new item
531 postpone [ bail out ]
534 else if ((desc->action == svn_wc_conflict_action_add)
535 && (desc->reason == svn_wc_conflict_reason_obstructed))
537 const char *answer;
538 const char *prompt;
540 SVN_ERR(svn_cmdline_fprintf(
541 stderr, subpool,
542 _("Conflict discovered when trying to add '%s'.\n"
543 "An object of the same name already exists.\n"),
544 desc->path));
545 prompt = _("Select: (p) postpone, (mf) mine-full, "
546 "(tf) theirs-full, (h) help:");
548 while (1)
550 svn_pool_clear(subpool);
552 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool));
554 if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
556 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
557 _(" (p) postpone - resolve the conflict later\n"
558 " (mf) mine-full - accept pre-existing item "
559 "(ignore upstream addition)\n"
560 " (tf) theirs-full - accept incoming item "
561 "(overwrite pre-existing item)\n"
562 " (h) help - show this help\n\n")));
564 if (strcmp(answer, "p") == 0)
566 (*result)->choice = svn_wc_conflict_choose_postpone;
567 break;
569 if (strcmp(answer, "mf") == 0)
571 (*result)->choice = svn_wc_conflict_choose_mine_full;
572 break;
574 if (strcmp(answer, "tf") == 0)
576 (*result)->choice = svn_wc_conflict_choose_theirs_full;
577 break;
579 if (strcmp(answer, "mc") == 0)
581 SVN_ERR(svn_cmdline_fprintf
582 (stderr, subpool,
583 _("Sorry, '(mc) mine for conflicts' "
584 "is not yet implemented; see\n"
585 "http://subversion.tigris.org/issues/show_bug.cgi?id=3049\n\n")));
586 continue;
588 if (strcmp(answer, "tc") == 0)
590 SVN_ERR(svn_cmdline_fprintf
591 (stderr, subpool,
592 _("Sorry, '(tc) theirs for conflicts' "
593 "is not yet implemented; see\n"
594 "http://subversion.tigris.org/issues/show_bug.cgi?id=3049\n\n")));
595 continue;
600 else /* other types of conflicts -- do nothing about them. */
602 (*result)->choice = svn_wc_conflict_choose_postpone;
605 svn_pool_destroy(subpool);
606 return SVN_NO_ERROR;