In the command-line client, forbid
[svn.git] / subversion / svn / conflict-callbacks.c
blob118a933394faa03cdc35a1c0f876c32235d6aad2
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_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;
77 static svn_error_t *
78 show_diff(svn_boolean_t *performed_edit,
79 const svn_wc_conflict_description_t *desc,
80 apr_pool_t *pool)
82 const char *path1, *path2;
83 svn_diff_t *diff;
84 svn_stream_t *output;
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;
93 else
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,
105 options, pool));
106 SVN_ERR(svn_diff_file_output_unified2(output, diff,
107 path1, path2,
108 NULL, NULL,
109 APR_LOCALE_CHARSET,
110 pool));
112 *performed_edit = TRUE;
114 return SVN_NO_ERROR;
118 static svn_error_t *
119 open_editor(svn_boolean_t *performed_edit,
120 const svn_wc_conflict_description_t *desc,
121 svn_cl__conflict_baton_t *b,
122 apr_pool_t *pool)
124 svn_error_t *err;
126 if (desc->merged_file)
128 err = svn_cl__edit_file_externally(desc->merged_file, b->editor_cmd,
129 b->config, pool);
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);
144 else if (err)
145 return err;
146 else
147 *performed_edit = TRUE;
149 else
150 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
151 _("Invalid option; there's no "
152 "merged version to edit.\n\n")));
154 return SVN_NO_ERROR;
158 static svn_error_t *
159 launch_resolver(svn_boolean_t *performed_edit,
160 const svn_wc_conflict_description_t *desc,
161 svn_cl__conflict_baton_t *b,
162 apr_pool_t *pool)
164 svn_error_t *err;
166 err = svn_cl__merge_file_externally(desc->base_file, desc->their_file,
167 desc->my_file, desc->merged_file,
168 b->config, pool);
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);
183 else if (err)
184 return err;
185 else if (performed_edit)
186 *performed_edit = TRUE;
188 return SVN_NO_ERROR;
192 /* Implement svn_wc_conflict_resolver_func_t; resolves based on
193 --accept option if given, else by prompting. */
194 svn_error_t *
195 svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
196 const svn_wc_conflict_description_t *desc,
197 void *baton,
198 apr_pool_t *pool)
200 svn_cl__conflict_baton_t *b = baton;
201 svn_error_t *err;
202 apr_pool_t *subpool;
204 /* Start out assuming we're going to postpone the conflict. */
205 *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
206 NULL, pool);
208 switch (b->accept_which)
210 case svn_cl__accept_invalid:
211 /* No --accept option, fall through to prompting. */
212 break;
213 case svn_cl__accept_postpone:
214 (*result)->choice = svn_wc_conflict_choose_postpone;
215 return SVN_NO_ERROR;
216 case svn_cl__accept_base:
217 (*result)->choice = svn_wc_conflict_choose_base;
218 return SVN_NO_ERROR;
219 case svn_cl__accept_mine:
220 (*result)->choice = svn_wc_conflict_choose_mine;
221 return SVN_NO_ERROR;
222 case svn_cl__accept_theirs:
223 (*result)->choice = svn_wc_conflict_choose_theirs;
224 return SVN_NO_ERROR;
225 case svn_cl__accept_edit:
226 if (desc->merged_file)
228 if (b->external_failed)
230 (*result)->choice = svn_wc_conflict_choose_postpone;
231 return SVN_NO_ERROR;
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 :
240 _("No editor found,"
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;
254 else if (err)
255 return err;
256 (*result)->choice = svn_wc_conflict_choose_merged;
257 return SVN_NO_ERROR;
259 /* else, fall through to prompting. */
260 break;
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;
268 return SVN_NO_ERROR;
271 err = svn_cl__merge_file_externally(desc->base_file,
272 desc->their_file,
273 desc->my_file,
274 desc->merged_file,
275 b->config,
276 pool);
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;
295 else if (err)
296 return err;
298 (*result)->choice = svn_wc_conflict_choose_merged;
299 return SVN_NO_ERROR;
301 /* else, fall through to prompting. */
302 break;
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))
319 const char *answer;
320 char *prompt;
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"),
327 desc->path));
328 else if (desc->kind == svn_wc_conflict_kind_property)
330 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
331 _("Conflict for property '%s' discovered"
332 " on '%s'.\n"),
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;
343 if (desc->my_file)
345 SVN_ERR(svn_stringbuf_from_file(&myval, desc->my_file,
346 subpool));
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"),
350 myval->data));
352 else
354 SVN_ERR(svn_stringbuf_from_file(&theirval, desc->their_file,
355 subpool));
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"),
359 theirval->data));
363 else
364 /* We don't recognize any other sort of conflict yet */
365 return SVN_NO_ERROR;
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))
373 diff_allowed = TRUE;
375 while (TRUE)
377 svn_pool_clear(subpool);
379 prompt = apr_pstrdup(subpool, _("Select: (p)ostpone"));
380 if (diff_allowed)
381 prompt = apr_pstrcat(subpool, prompt, _(", (d)iff, (e)dit"),
382 NULL);
383 else
384 prompt = apr_pstrcat(subpool, prompt, _(", (m)ine, (t)heirs"),
385 NULL);
386 if (performed_edit)
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. */
394 if (answer[1] != 0)
395 continue;
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;
413 break;
415 else if (answer[0] == 'm')
417 (*result)->choice = svn_wc_conflict_choose_mine;
418 break;
420 else if (answer[0] == 't')
422 (*result)->choice = svn_wc_conflict_choose_theirs;
423 break;
425 else if (answer[0] == 'd')
427 if (! diff_allowed)
429 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
430 _("Invalid option; there's no "
431 "merged version to diff.\n\n")));
432 continue;
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));
446 else
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
454 the diff. */
455 if (performed_edit)
457 (*result)->choice = svn_wc_conflict_choose_merged;
458 break;
460 else
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
475 add existing item
477 choose_theirs destroy file, schedule-delete, revert add,
478 add new item. add new item. rm file,
479 add new item
481 postpone [ bail out ]
484 else if ((desc->action == svn_wc_conflict_action_add)
485 && (desc->reason == svn_wc_conflict_reason_obstructed))
487 const char *answer;
488 const char *prompt;
490 SVN_ERR(svn_cmdline_fprintf(
491 stderr, subpool,
492 _("Conflict discovered when trying to add '%s'.\n"
493 "An object of the same name already exists.\n"),
494 desc->path));
495 prompt = _("Select: (p)ostpone, (m)ine, (t)heirs, (h)elp :");
497 while (1)
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;
514 break;
516 if (strcmp(answer, "m") == 0)
518 (*result)->choice = svn_wc_conflict_choose_mine;
519 break;
521 if (strcmp(answer, "t") == 0)
523 (*result)->choice = svn_wc_conflict_choose_theirs;
524 break;
529 else /* other types of conflicts -- do nothing about them. */
531 (*result)->choice = svn_wc_conflict_choose_postpone;
534 svn_pool_destroy(subpool);
535 return SVN_NO_ERROR;