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_MINE
) == 0)
65 return svn_cl__accept_mine
;
66 if (strcmp(word
, SVN_CL__ACCEPT_THEIRS
) == 0)
67 return svn_cl__accept_theirs
;
68 if (strcmp(word
, SVN_CL__ACCEPT_EDIT
) == 0)
69 return svn_cl__accept_edit
;
70 if (strcmp(word
, SVN_CL__ACCEPT_LAUNCH
) == 0)
71 return svn_cl__accept_launch
;
72 /* word is an invalid action. */
73 return svn_cl__accept_invalid
;
78 show_diff(svn_boolean_t
*performed_edit
,
79 const svn_wc_conflict_description_t
*desc
,
82 const char *path1
, *path2
;
85 svn_diff_file_options_t
*options
;
87 if (desc
->merged_file
&& desc
->base_file
)
89 /* Show the conflict markers to the user */
90 path1
= desc
->base_file
;
91 path2
= desc
->merged_file
;
95 /* There's no base file, but we can show the
96 difference between mine and theirs. */
97 path1
= desc
->their_file
;
98 path2
= desc
->my_file
;
101 options
= svn_diff_file_options_create(pool
);
102 options
->ignore_eol_style
= TRUE
;
103 SVN_ERR(svn_stream_for_stdout(&output
, pool
));
104 SVN_ERR(svn_diff_file_diff_2(&diff
, path1
, path2
,
106 SVN_ERR(svn_diff_file_output_unified2(output
, diff
,
112 *performed_edit
= TRUE
;
119 open_editor(svn_boolean_t
*performed_edit
,
120 const svn_wc_conflict_description_t
*desc
,
121 svn_cl__conflict_baton_t
*b
,
126 if (desc
->merged_file
)
128 err
= svn_cl__edit_file_externally(desc
->merged_file
, b
->editor_cmd
,
130 if (err
&& (err
->apr_err
== SVN_ERR_CL_NO_EXTERNAL_EDITOR
))
132 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
, "%s\n",
133 err
->message
? err
->message
:
134 _("No editor found.")));
135 svn_error_clear(err
);
137 else if (err
&& (err
->apr_err
== SVN_ERR_EXTERNAL_PROGRAM
))
139 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
, "%s\n",
140 err
->message
? err
->message
:
141 _("Error running editor.")));
142 svn_error_clear(err
);
147 *performed_edit
= TRUE
;
150 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
,
151 _("Invalid option; there's no "
152 "merged version to edit.\n\n")));
159 launch_resolver(svn_boolean_t
*performed_edit
,
160 const svn_wc_conflict_description_t
*desc
,
161 svn_cl__conflict_baton_t
*b
,
166 err
= svn_cl__merge_file_externally(desc
->base_file
, desc
->their_file
,
167 desc
->my_file
, desc
->merged_file
,
169 if (err
&& err
->apr_err
== SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL
)
171 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
, "%s\n",
172 err
->message
? err
->message
:
173 _("No merge tool found.\n")));
174 svn_error_clear(err
);
176 else if (err
&& err
->apr_err
== SVN_ERR_EXTERNAL_PROGRAM
)
178 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
, "%s\n",
179 err
->message
? err
->message
:
180 _("Error running merge tool.")));
181 svn_error_clear(err
);
185 else if (performed_edit
)
186 *performed_edit
= TRUE
;
192 /* Implement svn_wc_conflict_resolver_func_t; resolves based on
193 --accept option if given, else by prompting. */
195 svn_cl__conflict_handler(svn_wc_conflict_result_t
**result
,
196 const svn_wc_conflict_description_t
*desc
,
200 svn_cl__conflict_baton_t
*b
= baton
;
204 /* Start out assuming we're going to postpone the conflict. */
205 *result
= svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone
,
208 switch (b
->accept_which
)
210 case svn_cl__accept_invalid
:
211 /* No --accept option, fall through to prompting. */
213 case svn_cl__accept_postpone
:
214 (*result
)->choice
= svn_wc_conflict_choose_postpone
;
216 case svn_cl__accept_base
:
217 (*result
)->choice
= svn_wc_conflict_choose_base
;
219 case svn_cl__accept_mine
:
220 (*result
)->choice
= svn_wc_conflict_choose_mine
;
222 case svn_cl__accept_theirs
:
223 (*result
)->choice
= svn_wc_conflict_choose_theirs
;
225 case svn_cl__accept_edit
:
226 if (desc
->merged_file
)
228 if (b
->external_failed
)
230 (*result
)->choice
= svn_wc_conflict_choose_postpone
;
234 err
= svn_cl__edit_file_externally(desc
->merged_file
,
235 b
->editor_cmd
, b
->config
, pool
);
236 if (err
&& (err
->apr_err
== SVN_ERR_CL_NO_EXTERNAL_EDITOR
))
238 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
, "%s\n",
239 err
->message
? err
->message
:
241 " leaving all conflicts.")));
242 svn_error_clear(err
);
243 b
->external_failed
= TRUE
;
245 else if (err
&& (err
->apr_err
== SVN_ERR_EXTERNAL_PROGRAM
))
247 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
, "%s\n",
248 err
->message
? err
->message
:
249 _("Error running editor,"
250 " leaving all conflicts.")));
251 svn_error_clear(err
);
252 b
->external_failed
= TRUE
;
256 (*result
)->choice
= svn_wc_conflict_choose_merged
;
259 /* else, fall through to prompting. */
261 case svn_cl__accept_launch
:
262 if (desc
->base_file
&& desc
->their_file
263 && desc
->my_file
&& desc
->merged_file
)
265 if (b
->external_failed
)
267 (*result
)->choice
= svn_wc_conflict_choose_postpone
;
271 err
= svn_cl__merge_file_externally(desc
->base_file
,
277 if (err
&& err
->apr_err
== SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL
)
279 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
, "%s\n",
280 err
->message
? err
->message
:
281 _("No merge tool found,"
282 " leaving all conflicts.")));
283 svn_error_clear(err
);
284 b
->external_failed
= TRUE
;
286 else if (err
&& err
->apr_err
== SVN_ERR_EXTERNAL_PROGRAM
)
288 SVN_ERR(svn_cmdline_fprintf(stderr
, pool
, "%s\n",
289 err
->message
? err
->message
:
290 _("Error running merge tool"
291 " leaving all conflicts.")));
292 svn_error_clear(err
);
293 b
->external_failed
= TRUE
;
298 (*result
)->choice
= svn_wc_conflict_choose_merged
;
301 /* else, fall through to prompting. */
305 /* We're in interactive mode and either the user gave no --accept
306 option or the option did not apply; let's prompt. */
307 subpool
= svn_pool_create(pool
);
309 /* Handle the most common cases, which is either:
311 Conflicting edits on a file's text, or
312 Conflicting edits on a property.
314 if (((desc
->node_kind
== svn_node_file
)
315 && (desc
->action
== svn_wc_conflict_action_edit
)
316 && (desc
->reason
== svn_wc_conflict_reason_edited
))
317 || (desc
->kind
== svn_wc_conflict_kind_property
))
321 svn_boolean_t diff_allowed
= FALSE
;
322 svn_boolean_t performed_edit
= FALSE
;
324 if (desc
->kind
== svn_wc_conflict_kind_text
)
325 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
326 _("Conflict discovered in '%s'.\n"),
328 else if (desc
->kind
== svn_wc_conflict_kind_property
)
330 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
331 _("Conflict for property '%s' discovered"
333 desc
->property_name
, desc
->path
));
335 if ((!desc
->my_file
&& desc
->their_file
)
336 || (desc
->my_file
&& !desc
->their_file
))
338 /* One agent wants to change the property, one wants to
339 delete it. This is not something we can diff, so we
340 just tell the user. */
341 svn_stringbuf_t
*myval
= NULL
, *theirval
= NULL
;
345 SVN_ERR(svn_stringbuf_from_file(&myval
, desc
->my_file
,
347 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
348 _("They want to delete the property, "
349 "you want to change the value to '%s'.\n"),
354 SVN_ERR(svn_stringbuf_from_file(&theirval
, desc
->their_file
,
356 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
357 _("They want to change the property value to '%s', "
358 "you want to delete the property.\n"),
364 /* We don't recognize any other sort of conflict yet */
367 /* Diffing can happen between base and merged, to show conflict
368 markers to the user (this is the typical 3-way merge
369 scenario), or if no base is available, we can show a diff
370 between mine and theirs. */
371 if ((desc
->merged_file
&& desc
->base_file
)
372 || (!desc
->base_file
&& desc
->my_file
&& desc
->their_file
))
377 svn_pool_clear(subpool
);
379 prompt
= apr_pstrdup(subpool
, _("Select: (p)ostpone"));
381 prompt
= apr_pstrcat(subpool
, prompt
, _(", (d)iff, (e)dit"),
384 prompt
= apr_pstrcat(subpool
, prompt
, _(", (m)ine, (t)heirs"),
387 prompt
= apr_pstrcat(subpool
, prompt
, _(", (r)esolved"), NULL
);
388 prompt
= apr_pstrcat(subpool
, prompt
,
389 _(", (h)elp for more options : "), NULL
);
391 SVN_ERR(svn_cmdline_prompt_user2(&answer
, prompt
, b
->pb
, subpool
));
393 /* Check for single charater response. */
397 if ((answer
[0] == 'h') || (answer
[0] == '?'))
399 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
400 _(" (p)ostpone - mark the conflict to be resolved later\n"
401 " (d)iff - show all changes made to merged file\n"
402 " (e)dit - change merged file in an editor\n"
403 " (r)esolved - accept merged version of file\n"
404 " (m)ine - accept my version of file\n"
405 " (t)heirs - accept their version of file\n"
406 " (l)aunch - use third-party tool to resolve conflict\n"
407 " (h)elp - show this list\n\n")));
409 else if (answer
[0] == 'p')
411 /* Do nothing, let file be marked conflicted. */
412 (*result
)->choice
= svn_wc_conflict_choose_postpone
;
415 else if (answer
[0] == 'm')
417 (*result
)->choice
= svn_wc_conflict_choose_mine
;
420 else if (answer
[0] == 't')
422 (*result
)->choice
= svn_wc_conflict_choose_theirs
;
425 else if (answer
[0] == 'd')
429 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
430 _("Invalid option; there's no "
431 "merged version to diff.\n\n")));
435 SVN_ERR(show_diff(&performed_edit
, desc
, subpool
));
437 else if (answer
[0] == 'e')
439 SVN_ERR(open_editor(&performed_edit
, desc
, b
, subpool
));
441 else if (answer
[0] == 'l')
443 if (desc
->base_file
&& desc
->their_file
&& desc
->my_file
444 && desc
->merged_file
)
445 SVN_ERR(launch_resolver(&performed_edit
, desc
, b
, subpool
));
447 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
448 _("Invalid option.\n\n")));
450 else if (answer
[0] == 'r')
452 /* We only allow the user accept the merged version of
453 the file if they've edited it, or at least looked at
457 (*result
)->choice
= svn_wc_conflict_choose_merged
;
461 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
462 _("Invalid option.\n\n")));
467 Dealing with obstruction of additions can be tricky. The
468 obstructing item could be unversioned, versioned, or even
469 schedule-add. Here's a matrix of how the caller should behave,
470 based on results we return.
472 Unversioned Versioned Schedule-Add
474 choose_mine skip addition, skip addition skip addition
477 choose_theirs destroy file, schedule-delete, revert add,
478 add new item. add new item. rm file,
481 postpone [ bail out ]
484 else if ((desc
->action
== svn_wc_conflict_action_add
)
485 && (desc
->reason
== svn_wc_conflict_reason_obstructed
))
490 SVN_ERR(svn_cmdline_fprintf(
492 _("Conflict discovered when trying to add '%s'.\n"
493 "An object of the same name already exists.\n"),
495 prompt
= _("Select: (p)ostpone, (m)ine, (t)heirs, (h)elp :");
499 svn_pool_clear(subpool
);
501 SVN_ERR(svn_cmdline_prompt_user2(&answer
, prompt
, b
->pb
, subpool
));
503 if ((strcmp(answer
, "h") == 0) || (strcmp(answer
, "?") == 0))
505 SVN_ERR(svn_cmdline_fprintf(stderr
, subpool
,
506 _(" (p)ostpone - resolve the conflict later\n"
507 " (m)ine - accept pre-existing item\n"
508 " (t)heirs - accept incoming item\n"
509 " (h)elp - show this list\n\n")));
511 if (strcmp(answer
, "p") == 0)
513 (*result
)->choice
= svn_wc_conflict_choose_postpone
;
516 if (strcmp(answer
, "m") == 0)
518 (*result
)->choice
= svn_wc_conflict_choose_mine
;
521 if (strcmp(answer
, "t") == 0)
523 (*result
)->choice
= svn_wc_conflict_choose_theirs
;
529 else /* other types of conflicts -- do nothing about them. */
531 (*result
)->choice
= svn_wc_conflict_choose_postpone
;
534 svn_pool_destroy(subpool
);