2 * conflict-callbacks.c: conflict resolution callbacks specific to the
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 /* ==================================================================== */
26 #define APR_WANT_STDIO
27 #define APR_WANT_STRFUNC
30 #include "svn_cmdline.h"
31 #include "svn_client.h"
32 #include "svn_types.h"
33 #include "svn_pools.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
,
44 const char *editor_cmd
,
45 svn_cmdline_prompt_baton_t
*pb
,
48 svn_cl__conflict_baton_t
*b
= apr_palloc(pool
, sizeof(*b
));
49 b
->accept_which
= accept_which
;
51 b
->editor_cmd
= editor_cmd
;
52 b
->external_failed
= FALSE
;
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
;
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
;
86 show_diff(svn_boolean_t
*performed_edit
,
87 const svn_wc_conflict_description_t
*desc
,
90 const char *path1
, *path2
;
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
;
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
,
114 SVN_ERR(svn_diff_file_output_unified2(output
, diff
,
120 *performed_edit
= TRUE
;
127 open_editor(svn_boolean_t
*performed_edit
,
128 const svn_wc_conflict_description_t
*desc
,
129 svn_cl__conflict_baton_t
*b
,
134 if (desc
->merged_file
)
136 err
= svn_cl__edit_file_externally(desc
->merged_file
, b
->editor_cmd
,
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
);
155 *performed_edit
= TRUE
;
158 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
,
159 _("Invalid option; there's no "
160 "merged version to edit.\n\n")));
167 launch_resolver(svn_boolean_t
*performed_edit
,
168 const svn_wc_conflict_description_t
*desc
,
169 svn_cl__conflict_baton_t
*b
,
174 err
= svn_cl__merge_file_externally(desc
->base_file
, desc
->their_file
,
175 desc
->my_file
, desc
->merged_file
,
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
);
193 else if (performed_edit
)
194 *performed_edit
= TRUE
;
200 /* Implement svn_wc_conflict_resolver_func_t; resolves based on
201 --accept option if given, else by prompting. */
203 svn_cl__conflict_handler(svn_wc_conflict_result_t
**result
,
204 const svn_wc_conflict_description_t
*desc
,
208 svn_cl__conflict_baton_t
*b
= baton
;
212 /* Start out assuming we're going to postpone the conflict. */
213 *result
= svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone
,
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. */
222 case svn_cl__accept_postpone
:
223 (*result
)->choice
= svn_wc_conflict_choose_postpone
;
225 case svn_cl__accept_base
:
226 (*result
)->choice
= svn_wc_conflict_choose_base
;
228 case svn_cl__accept_working
:
229 (*result
)->choice
= svn_wc_conflict_choose_merged
;
231 case svn_cl__accept_mine_conflict
:
232 (*result
)->choice
= svn_wc_conflict_choose_mine_conflict
;
234 case svn_cl__accept_theirs_conflict
:
235 (*result
)->choice
= svn_wc_conflict_choose_theirs_conflict
;
237 case svn_cl__accept_mine_full
:
238 (*result
)->choice
= svn_wc_conflict_choose_mine_full
;
240 case svn_cl__accept_theirs_full
:
241 (*result
)->choice
= svn_wc_conflict_choose_theirs_full
;
243 case svn_cl__accept_edit
:
244 if (desc
->merged_file
)
246 if (b
->external_failed
)
248 (*result
)->choice
= svn_wc_conflict_choose_postpone
;
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
:
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
;
274 (*result
)->choice
= svn_wc_conflict_choose_merged
;
277 /* else, fall through to prompting. */
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
;
289 err
= svn_cl__merge_file_externally(desc
->base_file
,
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
;
316 (*result
)->choice
= svn_wc_conflict_choose_merged
;
319 /* else, fall through to prompting. */
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
))
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"),
346 else if (desc
->kind
== svn_wc_conflict_kind_property
)
348 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
349 _("Conflict for property '%s' discovered"
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
;
363 SVN_ERR(svn_stringbuf_from_file(&myval
, desc
->my_file
,
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"),
372 SVN_ERR(svn_stringbuf_from_file(&theirval
, desc
->their_file
,
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"),
382 /* We don't recognize any other sort of conflict yet */
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
))
395 svn_pool_clear(subpool
);
397 prompt
= apr_pstrdup(subpool
, _("Select: (p) postpone"));
399 prompt
= apr_pstrcat(subpool
, prompt
,
400 _(", (df) diff-full, (e) edit"),
403 prompt
= apr_pstrcat(subpool
, prompt
,
404 _(", (mf) mine-full, (tf) theirs-full"),
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: "),
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 "
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 "
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
;
438 else if (strcmp(answer
, "mc") == 0)
440 SVN_ERR(svn_cmdline_fprintf
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")));
447 else if (strcmp(answer
, "tc") == 0)
449 SVN_ERR(svn_cmdline_fprintf
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")));
456 else if (strcmp(answer
, "mf") == 0)
458 (*result
)->choice
= svn_wc_conflict_choose_mine_full
;
461 else if (strcmp(answer
, "tf") == 0)
463 (*result
)->choice
= svn_wc_conflict_choose_theirs_full
;
466 else if (strcmp(answer
, "dc") == 0)
468 SVN_ERR(svn_cmdline_fprintf
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")));
475 else if (strcmp(answer
, "df") == 0)
479 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
480 _("Invalid option; there's no "
481 "merged version to diff.\n\n")));
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
));
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
507 (*result
)->choice
= svn_wc_conflict_choose_merged
;
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
527 choose_theirs destroy file, schedule-delete, revert add,
528 add new item. add new item. rm file,
531 postpone [ bail out ]
534 else if ((desc
->action
== svn_wc_conflict_action_add
)
535 && (desc
->reason
== svn_wc_conflict_reason_obstructed
))
540 SVN_ERR(svn_cmdline_fprintf(
542 _("Conflict discovered when trying to add '%s'.\n"
543 "An object of the same name already exists.\n"),
545 prompt
= _("Select: (p) postpone, (mf) mine-full, "
546 "(tf) theirs-full, (h) help:");
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
;
569 if (strcmp(answer
, "mf") == 0)
571 (*result
)->choice
= svn_wc_conflict_choose_mine_full
;
574 if (strcmp(answer
, "tf") == 0)
576 (*result
)->choice
= svn_wc_conflict_choose_theirs_full
;
579 if (strcmp(answer
, "mc") == 0)
581 SVN_ERR(svn_cmdline_fprintf
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")));
588 if (strcmp(answer
, "tc") == 0)
590 SVN_ERR(svn_cmdline_fprintf
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")));
600 else /* other types of conflicts -- do nothing about them. */
602 (*result
)->choice
= svn_wc_conflict_choose_postpone
;
605 svn_pool_destroy(subpool
);