1251881Speter/*
2251881Speter * conflict-callbacks.c: conflict resolution callbacks specific to the
3251881Speter * commandline client.
4251881Speter *
5251881Speter * ====================================================================
6251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
7251881Speter *    or more contributor license agreements.  See the NOTICE file
8251881Speter *    distributed with this work for additional information
9251881Speter *    regarding copyright ownership.  The ASF licenses this file
10251881Speter *    to you under the Apache License, Version 2.0 (the
11251881Speter *    "License"); you may not use this file except in compliance
12251881Speter *    with the License.  You may obtain a copy of the License at
13251881Speter *
14251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
15251881Speter *
16251881Speter *    Unless required by applicable law or agreed to in writing,
17251881Speter *    software distributed under the License is distributed on an
18251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19251881Speter *    KIND, either express or implied.  See the License for the
20251881Speter *    specific language governing permissions and limitations
21251881Speter *    under the License.
22251881Speter * ====================================================================
23251881Speter */
24251881Speter
25251881Speter#include <apr_xlate.h>  /* for APR_LOCALE_CHARSET */
26251881Speter
27251881Speter#define APR_WANT_STRFUNC
28251881Speter#include <apr_want.h>
29251881Speter
30251881Speter#include "svn_hash.h"
31251881Speter#include "svn_cmdline.h"
32251881Speter#include "svn_client.h"
33251881Speter#include "svn_dirent_uri.h"
34251881Speter#include "svn_types.h"
35251881Speter#include "svn_pools.h"
36251881Speter#include "svn_sorts.h"
37251881Speter#include "svn_utf.h"
38251881Speter
39251881Speter#include "cl.h"
40251881Speter#include "cl-conflicts.h"
41251881Speter
42251881Speter#include "private/svn_cmdline_private.h"
43251881Speter
44251881Speter#include "svn_private_config.h"
45251881Speter
46251881Speter#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0])))
47251881Speter
48251881Speter
49251881Speter
50251881Speterstruct svn_cl__interactive_conflict_baton_t {
51251881Speter  svn_cl__accept_t accept_which;
52251881Speter  apr_hash_t *config;
53251881Speter  const char *editor_cmd;
54251881Speter  svn_boolean_t external_failed;
55251881Speter  svn_cmdline_prompt_baton_t *pb;
56251881Speter  const char *path_prefix;
57251881Speter  svn_boolean_t quit;
58251881Speter  svn_cl__conflict_stats_t *conflict_stats;
59251881Speter};
60251881Speter
61251881Spetersvn_error_t *
62251881Spetersvn_cl__get_conflict_func_interactive_baton(
63251881Speter  svn_cl__interactive_conflict_baton_t **b,
64251881Speter  svn_cl__accept_t accept_which,
65251881Speter  apr_hash_t *config,
66251881Speter  const char *editor_cmd,
67251881Speter  svn_cl__conflict_stats_t *conflict_stats,
68251881Speter  svn_cancel_func_t cancel_func,
69251881Speter  void *cancel_baton,
70251881Speter  apr_pool_t *result_pool)
71251881Speter{
72251881Speter  svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb));
73251881Speter  pb->cancel_func = cancel_func;
74251881Speter  pb->cancel_baton = cancel_baton;
75251881Speter
76251881Speter  *b = apr_palloc(result_pool, sizeof(**b));
77251881Speter  (*b)->accept_which = accept_which;
78251881Speter  (*b)->config = config;
79251881Speter  (*b)->editor_cmd = editor_cmd;
80251881Speter  (*b)->external_failed = FALSE;
81251881Speter  (*b)->pb = pb;
82251881Speter  SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool));
83251881Speter  (*b)->quit = FALSE;
84251881Speter  (*b)->conflict_stats = conflict_stats;
85251881Speter
86251881Speter  return SVN_NO_ERROR;
87251881Speter}
88251881Speter
89251881Spetersvn_cl__accept_t
90251881Spetersvn_cl__accept_from_word(const char *word)
91251881Speter{
92251881Speter  /* Shorthand options are consistent with  svn_cl__conflict_handler(). */
93251881Speter  if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0
94251881Speter      || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0)
95251881Speter    return svn_cl__accept_postpone;
96251881Speter  if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0)
97251881Speter    /* ### shorthand? */
98251881Speter    return svn_cl__accept_base;
99251881Speter  if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0)
100251881Speter    /* ### shorthand? */
101251881Speter    return svn_cl__accept_working;
102251881Speter  if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0
103251881Speter      || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0)
104251881Speter    return svn_cl__accept_mine_conflict;
105251881Speter  if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0
106251881Speter      || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0)
107251881Speter    return svn_cl__accept_theirs_conflict;
108251881Speter  if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0
109251881Speter      || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0)
110251881Speter    return svn_cl__accept_mine_full;
111251881Speter  if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0
112251881Speter      || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0)
113251881Speter    return svn_cl__accept_theirs_full;
114251881Speter  if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0
115251881Speter      || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0)
116251881Speter    return svn_cl__accept_edit;
117251881Speter  if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0
118251881Speter      || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0)
119251881Speter    return svn_cl__accept_launch;
120251881Speter  /* word is an invalid action. */
121251881Speter  return svn_cl__accept_invalid;
122251881Speter}
123251881Speter
124251881Speter
125251881Speter/* Print on stdout a diff that shows incoming conflicting changes
126251881Speter * corresponding to the conflict described in DESC. */
127251881Speterstatic svn_error_t *
128251881Spetershow_diff(const svn_wc_conflict_description2_t *desc,
129251881Speter          const char *path_prefix,
130251881Speter          apr_pool_t *pool)
131251881Speter{
132251881Speter  const char *path1, *path2;
133251881Speter  const char *label1, *label2;
134251881Speter  svn_diff_t *diff;
135251881Speter  svn_stream_t *output;
136251881Speter  svn_diff_file_options_t *options;
137251881Speter
138251881Speter  if (desc->merged_file)
139251881Speter    {
140251881Speter      /* For conflicts recorded by the 'merge' operation, show a diff between
141251881Speter       * 'mine' (the working version of the file as it appeared before the
142251881Speter       * 'merge' operation was run) and 'merged' (the version of the file
143251881Speter       * as it appears after the merge operation).
144251881Speter       *
145251881Speter       * For conflicts recorded by the 'update' and 'switch' operations,
146251881Speter       * show a diff beween 'theirs' (the new pristine version of the
147251881Speter       * file) and 'merged' (the version of the file as it appears with
148251881Speter       * local changes merged with the new pristine version).
149251881Speter       *
150251881Speter       * This way, the diff is always minimal and clearly identifies changes
151251881Speter       * brought into the working copy by the update/switch/merge operation. */
152251881Speter      if (desc->operation == svn_wc_operation_merge)
153251881Speter        {
154251881Speter          path1 = desc->my_abspath;
155251881Speter          label1 = _("MINE");
156251881Speter        }
157251881Speter      else
158251881Speter        {
159251881Speter          path1 = desc->their_abspath;
160251881Speter          label1 = _("THEIRS");
161251881Speter        }
162251881Speter      path2 = desc->merged_file;
163251881Speter      label2 = _("MERGED");
164251881Speter    }
165251881Speter  else
166251881Speter    {
167251881Speter      /* There's no merged file, but we can show the
168251881Speter         difference between mine and theirs. */
169251881Speter      path1 = desc->their_abspath;
170251881Speter      label1 = _("THEIRS");
171251881Speter      path2 = desc->my_abspath;
172251881Speter      label2 = _("MINE");
173251881Speter    }
174251881Speter
175251881Speter  label1 = apr_psprintf(pool, "%s\t- %s",
176251881Speter                        svn_cl__local_style_skip_ancestor(
177251881Speter                          path_prefix, path1, pool), label1);
178251881Speter  label2 = apr_psprintf(pool, "%s\t- %s",
179251881Speter                        svn_cl__local_style_skip_ancestor(
180251881Speter                          path_prefix, path2, pool), label2);
181251881Speter
182251881Speter  options = svn_diff_file_options_create(pool);
183251881Speter  options->ignore_eol_style = TRUE;
184251881Speter  SVN_ERR(svn_stream_for_stdout(&output, pool));
185251881Speter  SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2,
186251881Speter                               options, pool));
187251881Speter  return svn_diff_file_output_unified3(output, diff,
188251881Speter                                       path1, path2,
189251881Speter                                       label1, label2,
190251881Speter                                       APR_LOCALE_CHARSET,
191251881Speter                                       NULL, FALSE,
192251881Speter                                       pool);
193251881Speter}
194251881Speter
195251881Speter
196251881Speter/* Print on stdout just the conflict hunks of a diff among the 'base', 'their'
197251881Speter * and 'my' files of DESC. */
198251881Speterstatic svn_error_t *
199251881Spetershow_conflicts(const svn_wc_conflict_description2_t *desc,
200251881Speter               apr_pool_t *pool)
201251881Speter{
202251881Speter  svn_diff_t *diff;
203251881Speter  svn_stream_t *output;
204251881Speter  svn_diff_file_options_t *options;
205251881Speter
206251881Speter  options = svn_diff_file_options_create(pool);
207251881Speter  options->ignore_eol_style = TRUE;
208251881Speter  SVN_ERR(svn_stream_for_stdout(&output, pool));
209251881Speter  SVN_ERR(svn_diff_file_diff3_2(&diff,
210251881Speter                                desc->base_abspath,
211251881Speter                                desc->my_abspath,
212251881Speter                                desc->their_abspath,
213251881Speter                                options, pool));
214251881Speter  /* ### Consider putting the markers/labels from
215251881Speter     ### svn_wc__merge_internal in the conflict description. */
216251881Speter  return svn_diff_file_output_merge2(output, diff,
217251881Speter                                     desc->base_abspath,
218251881Speter                                     desc->my_abspath,
219251881Speter                                     desc->their_abspath,
220251881Speter                                     _("||||||| ORIGINAL"),
221251881Speter                                     _("<<<<<<< MINE (select with 'mc')"),
222251881Speter                                     _(">>>>>>> THEIRS (select with 'tc')"),
223251881Speter                                     "=======",
224251881Speter                                     svn_diff_conflict_display_only_conflicts,
225251881Speter                                     pool);
226251881Speter}
227251881Speter
228251881Speter/* Perform a 3-way merge of the conflicting values of a property,
229251881Speter * and write the result to the OUTPUT stream.
230251881Speter *
231251881Speter * If MERGED_ABSPATH is non-NULL, use it as 'my' version instead of
232251881Speter * DESC->MY_ABSPATH.
233251881Speter *
234251881Speter * Assume the values are printable UTF-8 text.
235251881Speter */
236251881Speterstatic svn_error_t *
237251881Spetermerge_prop_conflict(svn_stream_t *output,
238251881Speter                    const svn_wc_conflict_description2_t *desc,
239251881Speter                    const char *merged_abspath,
240251881Speter                    apr_pool_t *pool)
241251881Speter{
242251881Speter  const char *base_abspath = desc->base_abspath;
243251881Speter  const char *my_abspath = desc->my_abspath;
244251881Speter  const char *their_abspath = desc->their_abspath;
245251881Speter  svn_diff_file_options_t *options = svn_diff_file_options_create(pool);
246251881Speter  svn_diff_t *diff;
247251881Speter
248251881Speter  /* If any of the property values is missing, use an empty file instead
249251881Speter   * for the purpose of showing a diff. */
250251881Speter  if (! base_abspath || ! my_abspath || ! their_abspath)
251251881Speter    {
252251881Speter      const char *empty_file;
253251881Speter
254251881Speter      SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file,
255251881Speter                                       NULL, svn_io_file_del_on_pool_cleanup,
256251881Speter                                       pool, pool));
257251881Speter      if (! base_abspath)
258251881Speter        base_abspath = empty_file;
259251881Speter      if (! my_abspath)
260251881Speter        my_abspath = empty_file;
261251881Speter      if (! their_abspath)
262251881Speter        their_abspath = empty_file;
263251881Speter    }
264251881Speter
265251881Speter  options->ignore_eol_style = TRUE;
266251881Speter  SVN_ERR(svn_diff_file_diff3_2(&diff,
267251881Speter                                base_abspath,
268251881Speter                                merged_abspath ? merged_abspath : my_abspath,
269251881Speter                                their_abspath,
270251881Speter                                options, pool));
271251881Speter  SVN_ERR(svn_diff_file_output_merge2(output, diff,
272251881Speter                                      base_abspath,
273251881Speter                                      merged_abspath ? merged_abspath
274251881Speter                                                     : my_abspath,
275251881Speter                                      their_abspath,
276251881Speter                                      _("||||||| ORIGINAL"),
277251881Speter                                      _("<<<<<<< MINE"),
278251881Speter                                      _(">>>>>>> THEIRS"),
279251881Speter                                      "=======",
280251881Speter                                      svn_diff_conflict_display_modified_original_latest,
281251881Speter                                      pool));
282251881Speter
283251881Speter  return SVN_NO_ERROR;
284251881Speter}
285251881Speter
286251881Speter/* Display the conflicting values of a property as a 3-way diff.
287251881Speter *
288251881Speter * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of
289251881Speter * DESC->MY_ABSPATH.
290251881Speter *
291251881Speter * Assume the values are printable UTF-8 text.
292251881Speter */
293251881Speterstatic svn_error_t *
294251881Spetershow_prop_conflict(const svn_wc_conflict_description2_t *desc,
295251881Speter                   const char *merged_abspath,
296251881Speter                   apr_pool_t *pool)
297251881Speter{
298251881Speter  svn_stream_t *output;
299251881Speter
300251881Speter  SVN_ERR(svn_stream_for_stdout(&output, pool));
301251881Speter  SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, pool));
302251881Speter
303251881Speter  return SVN_NO_ERROR;
304251881Speter}
305251881Speter
306251881Speter/* Run an external editor, passing it the MERGED_FILE, or, if the
307251881Speter * 'merged' file is null, return an error. The tool to use is determined by
308251881Speter * B->editor_cmd, B->config and environment variables; see
309251881Speter * svn_cl__edit_file_externally() for details.
310251881Speter *
311251881Speter * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
312251881Speter * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
313251881Speter * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
314251881Speter * return that error. */
315251881Speterstatic svn_error_t *
316251881Speteropen_editor(svn_boolean_t *performed_edit,
317251881Speter            const char *merged_file,
318251881Speter            svn_cl__interactive_conflict_baton_t *b,
319251881Speter            apr_pool_t *pool)
320251881Speter{
321251881Speter  svn_error_t *err;
322251881Speter
323251881Speter  if (merged_file)
324251881Speter    {
325251881Speter      err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd,
326251881Speter                                              b->config, pool);
327251881Speter      if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
328251881Speter        {
329251881Speter          svn_error_t *root_err = svn_error_root_cause(err);
330251881Speter
331251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
332251881Speter                                      root_err->message ? root_err->message :
333251881Speter                                      _("No editor found.")));
334251881Speter          svn_error_clear(err);
335251881Speter        }
336251881Speter      else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
337251881Speter        {
338251881Speter          svn_error_t *root_err = svn_error_root_cause(err);
339251881Speter
340251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
341251881Speter                                      root_err->message ? root_err->message :
342251881Speter                                      _("Error running editor.")));
343251881Speter          svn_error_clear(err);
344251881Speter        }
345251881Speter      else if (err)
346251881Speter        return svn_error_trace(err);
347251881Speter      else
348251881Speter        *performed_edit = TRUE;
349251881Speter    }
350251881Speter  else
351251881Speter    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
352251881Speter                                _("Invalid option; there's no "
353251881Speter                                  "merged version to edit.\n\n")));
354251881Speter
355251881Speter  return SVN_NO_ERROR;
356251881Speter}
357251881Speter
358251881Speter/* Run an external editor, passing it the 'merged' property in DESC.
359251881Speter * The tool to use is determined by B->editor_cmd, B->config and
360251881Speter * environment variables; see svn_cl__edit_file_externally() for details. */
361251881Speterstatic svn_error_t *
362251881Speteredit_prop_conflict(const char **merged_file_path,
363251881Speter                   const svn_wc_conflict_description2_t *desc,
364251881Speter                   svn_cl__interactive_conflict_baton_t *b,
365251881Speter                   apr_pool_t *result_pool,
366251881Speter                   apr_pool_t *scratch_pool)
367251881Speter{
368251881Speter  apr_file_t *file;
369251881Speter  const char *file_path;
370251881Speter  svn_boolean_t performed_edit = FALSE;
371251881Speter  svn_stream_t *merged_prop;
372251881Speter
373251881Speter  SVN_ERR(svn_io_open_unique_file3(&file, &file_path, NULL,
374251881Speter                                   svn_io_file_del_on_pool_cleanup,
375251881Speter                                   result_pool, scratch_pool));
376251881Speter  merged_prop = svn_stream_from_aprfile2(file, TRUE /* disown */,
377251881Speter                                         scratch_pool);
378251881Speter  SVN_ERR(merge_prop_conflict(merged_prop, desc, NULL, scratch_pool));
379251881Speter  SVN_ERR(svn_stream_close(merged_prop));
380251881Speter  SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
381251881Speter  SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool));
382251881Speter  *merged_file_path = (performed_edit ? file_path : NULL);
383251881Speter
384251881Speter  return SVN_NO_ERROR;
385251881Speter}
386251881Speter
387251881Speter/* Run an external merge tool, passing it the 'base', 'their', 'my' and
388251881Speter * 'merged' files in DESC. The tool to use is determined by B->config and
389251881Speter * environment variables; see svn_cl__merge_file_externally() for details.
390251881Speter *
391251881Speter * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
392251881Speter * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
393251881Speter * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
394251881Speter * return that error.  */
395251881Speterstatic svn_error_t *
396251881Speterlaunch_resolver(svn_boolean_t *performed_edit,
397251881Speter                const svn_wc_conflict_description2_t *desc,
398251881Speter                svn_cl__interactive_conflict_baton_t *b,
399251881Speter                apr_pool_t *pool)
400251881Speter{
401251881Speter  svn_error_t *err;
402251881Speter
403251881Speter  err = svn_cl__merge_file_externally(desc->base_abspath, desc->their_abspath,
404251881Speter                                      desc->my_abspath, desc->merged_file,
405251881Speter                                      desc->local_abspath, b->config, NULL,
406251881Speter                                      pool);
407251881Speter  if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
408251881Speter    {
409251881Speter      SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
410251881Speter                                  err->message ? err->message :
411251881Speter                                  _("No merge tool found, "
412251881Speter                                    "try '(m) merge' instead.\n")));
413251881Speter      svn_error_clear(err);
414251881Speter    }
415251881Speter  else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
416251881Speter    {
417251881Speter      SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
418251881Speter                                  err->message ? err->message :
419251881Speter                             _("Error running merge tool, "
420251881Speter                               "try '(m) merge' instead.")));
421251881Speter      svn_error_clear(err);
422251881Speter    }
423251881Speter  else if (err)
424251881Speter    return svn_error_trace(err);
425251881Speter  else if (performed_edit)
426251881Speter    *performed_edit = TRUE;
427251881Speter
428251881Speter  return SVN_NO_ERROR;
429251881Speter}
430251881Speter
431251881Speter
432251881Speter/* Maximum line length for the prompt string. */
433251881Speter#define MAX_PROMPT_WIDTH 70
434251881Speter
435251881Speter/* Description of a resolver option */
436251881Spetertypedef struct resolver_option_t
437251881Speter{
438251881Speter  const char *code;        /* one or two characters */
439251881Speter  const char *short_desc;  /* label in prompt (localized) */
440251881Speter  const char *long_desc;   /* longer description (localized) */
441251881Speter  svn_wc_conflict_choice_t choice;  /* or -1 if not a simple choice */
442251881Speter} resolver_option_t;
443251881Speter
444251881Speter/* Resolver options for a text conflict */
445251881Speter/* (opt->code == "" causes a blank line break in help_string()) */
446251881Speterstatic const resolver_option_t text_conflict_options[] =
447251881Speter{
448251881Speter  /* Translators: keep long_desc below 70 characters (wrap with a left
449251881Speter     margin of 9 spaces if needed); don't translate the words within square
450251881Speter     brackets. */
451251881Speter  { "e",  N_("edit file"),        N_("change merged file in an editor"
452251881Speter                                     "  [edit]"),
453251881Speter                                  -1 },
454251881Speter  { "df", N_("show diff"),        N_("show all changes made to merged file"),
455251881Speter                                  -1 },
456253734Speter  { "r",  N_("mark resolved"),   N_("accept merged version of file"),
457251881Speter                                  svn_wc_conflict_choose_merged },
458251881Speter  { "",   "",                     "", svn_wc_conflict_choose_unspecified },
459251881Speter  { "dc", N_("display conflict"), N_("show all conflicts "
460251881Speter                                     "(ignoring merged version)"), -1 },
461251881Speter  { "mc", N_("my side of conflict"), N_("accept my version for all conflicts "
462251881Speter                                        "(same)  [mine-conflict]"),
463251881Speter                                  svn_wc_conflict_choose_mine_conflict },
464251881Speter  { "tc", N_("their side of conflict"), N_("accept their version for all "
465251881Speter                                           "conflicts (same)"
466251881Speter                                           "  [theirs-conflict]"),
467251881Speter                                  svn_wc_conflict_choose_theirs_conflict },
468251881Speter  { "",   "",                     "", svn_wc_conflict_choose_unspecified },
469251881Speter  { "mf", N_("my version"),       N_("accept my version of entire file (even "
470251881Speter                                     "non-conflicts)  [mine-full]"),
471251881Speter                                  svn_wc_conflict_choose_mine_full },
472251881Speter  { "tf", N_("their version"),    N_("accept their version of entire file "
473251881Speter                                     "(same)  [theirs-full]"),
474251881Speter                                  svn_wc_conflict_choose_theirs_full },
475251881Speter  { "",   "",                     "", svn_wc_conflict_choose_unspecified },
476251881Speter  { "m",  N_("merge"),            N_("use internal merge tool to resolve "
477251881Speter                                     "conflict"), -1 },
478251881Speter  { "l",  N_("launch tool"),      N_("launch external tool to resolve "
479251881Speter                                     "conflict  [launch]"), -1 },
480253734Speter  { "p",  N_("postpone"),         N_("mark the conflict to be resolved later"
481253734Speter                                     "  [postpone]"),
482253734Speter                                  svn_wc_conflict_choose_postpone },
483251881Speter  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
484251881Speter                                  svn_wc_conflict_choose_postpone },
485251881Speter  { "s",  N_("show all options"), N_("show this list (also 'h', '?')"), -1 },
486251881Speter  { NULL }
487251881Speter};
488251881Speter
489251881Speter/* Resolver options for a property conflict */
490251881Speterstatic const resolver_option_t prop_conflict_options[] =
491251881Speter{
492262253Speter  { "mf", N_("my version"),       N_("accept my version of entire property (even "
493251881Speter                                     "non-conflicts)  [mine-full]"),
494251881Speter                                  svn_wc_conflict_choose_mine_full },
495262253Speter  { "tf", N_("their version"),    N_("accept their version of entire property "
496251881Speter                                     "(same)  [theirs-full]"),
497251881Speter                                  svn_wc_conflict_choose_theirs_full },
498251881Speter  { "dc", N_("display conflict"), N_("show conflicts in this property"), -1 },
499251881Speter  { "e",  N_("edit property"),    N_("change merged property value in an editor"
500251881Speter                                     "  [edit]"), -1 },
501253734Speter  { "r",  N_("mark resolved"),    N_("accept edited version of property"),
502251881Speter                                  svn_wc_conflict_choose_merged },
503253734Speter  { "p",  N_("postpone"),         N_("mark the conflict to be resolved later"
504253734Speter                                     "  [postpone]"),
505253734Speter                                  svn_wc_conflict_choose_postpone },
506251881Speter  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
507251881Speter                                  svn_wc_conflict_choose_postpone },
508251881Speter  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
509251881Speter  { NULL }
510251881Speter};
511251881Speter
512251881Speter/* Resolver options for a tree conflict */
513251881Speterstatic const resolver_option_t tree_conflict_options[] =
514251881Speter{
515253734Speter  { "r",  N_("mark resolved"),    N_("accept current working copy state"),
516253734Speter                                  svn_wc_conflict_choose_merged },
517251881Speter  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
518251881Speter                                  svn_wc_conflict_choose_postpone },
519251881Speter  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
520251881Speter                                  svn_wc_conflict_choose_postpone },
521251881Speter  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
522251881Speter  { NULL }
523251881Speter};
524251881Speter
525251881Speterstatic const resolver_option_t tree_conflict_options_update_moved_away[] =
526251881Speter{
527253734Speter  { "mc", N_("apply update (recommended)"),
528253734Speter                                  N_("apply update to the move destination"
529253734Speter                                     "  [mine-conflict]"),
530253734Speter                                  svn_wc_conflict_choose_mine_conflict },
531253734Speter  { "r",  N_("discard update (breaks move)"), N_("discard update, mark "
532253734Speter                                                 "resolved, the move will "
533253734Speter                                                 "will become a copy"),
534253734Speter                                  svn_wc_conflict_choose_merged },
535251881Speter  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
536251881Speter                                  svn_wc_conflict_choose_postpone },
537253734Speter  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
538253734Speter                                  svn_wc_conflict_choose_postpone },
539253734Speter  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
540253734Speter  { NULL }
541253734Speter};
542253734Speter
543253734Speterstatic const resolver_option_t tree_conflict_options_update_edit_moved_away[] =
544253734Speter{
545253734Speter  { "mc", N_("apply update to move destination"),
546253734Speter                                  N_("apply incoming update to move destination"
547253734Speter                                     "  [mine-conflict]"),
548251881Speter                                  svn_wc_conflict_choose_mine_conflict },
549253734Speter  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
550253734Speter                                  svn_wc_conflict_choose_postpone },
551251881Speter  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
552251881Speter                                  svn_wc_conflict_choose_postpone },
553251881Speter  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
554251881Speter  { NULL }
555251881Speter};
556251881Speter
557251881Speterstatic const resolver_option_t tree_conflict_options_update_deleted[] =
558251881Speter{
559253734Speter  { "mc", N_("keep affected local moves"), N_("keep any local moves affected "
560253734Speter                                              "by this deletion  [mine-conflict]"),
561253734Speter                                  svn_wc_conflict_choose_mine_conflict },
562253734Speter  { "r",  N_("mark resolved (breaks moves)"),  N_("mark resolved, any affected "
563253734Speter                                                  "moves will become copies"),
564253734Speter                                  svn_wc_conflict_choose_merged },
565251881Speter  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
566251881Speter                                  svn_wc_conflict_choose_postpone },
567251881Speter  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
568251881Speter                                  svn_wc_conflict_choose_postpone },
569251881Speter  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
570251881Speter  { NULL }
571251881Speter};
572251881Speter
573251881Speterstatic const resolver_option_t tree_conflict_options_update_replaced[] =
574251881Speter{
575253734Speter  { "mc", N_("keep affected local moves"), N_("keep any moves affected by this "
576253734Speter                                              "replacement  [mine-conflict]"),
577253734Speter                                  svn_wc_conflict_choose_mine_conflict },
578253734Speter  { "r",  N_("mark resolved (breaks moves)"), N_("mark resolved (any affected "
579253734Speter                                                 "moves will become copies)"),
580253734Speter                                  svn_wc_conflict_choose_merged },
581251881Speter  { "p",  N_("postpone"),         N_("resolve the conflict later  [postpone]"),
582251881Speter                                  svn_wc_conflict_choose_postpone },
583251881Speter  { "q",  N_("quit resolution"),  N_("postpone all remaining conflicts"),
584251881Speter                                  svn_wc_conflict_choose_postpone },
585251881Speter  { "h",  N_("help"),             N_("show this help (also '?')"), -1 },
586251881Speter  { NULL }
587251881Speter};
588251881Speter
589251881Speter
590251881Speter/* Return a pointer to the option description in OPTIONS matching the
591251881Speter * one- or two-character OPTION_CODE.  Return NULL if not found. */
592251881Speterstatic const resolver_option_t *
593251881Speterfind_option(const resolver_option_t *options,
594251881Speter            const char *option_code)
595251881Speter{
596251881Speter  const resolver_option_t *opt;
597251881Speter
598251881Speter  for (opt = options; opt->code; opt++)
599251881Speter    {
600251881Speter      /* Ignore code "" (blank lines) which is not a valid answer. */
601251881Speter      if (opt->code[0] && strcmp(opt->code, option_code) == 0)
602251881Speter        return opt;
603251881Speter    }
604251881Speter  return NULL;
605251881Speter}
606251881Speter
607251881Speter/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
608251881Speter * non-null, select only the options whose codes are mentioned in it. */
609251881Speterstatic const char *
610251881Speterprompt_string(const resolver_option_t *options,
611251881Speter              const char *const *option_codes,
612251881Speter              apr_pool_t *pool)
613251881Speter{
614251881Speter  const char *result = _("Select:");
615251881Speter  int left_margin = svn_utf_cstring_utf8_width(result);
616251881Speter  const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
617251881Speter  int this_line_len = left_margin;
618251881Speter  svn_boolean_t first = TRUE;
619251881Speter
620251881Speter  while (1)
621251881Speter    {
622251881Speter      const resolver_option_t *opt;
623251881Speter      const char *s;
624251881Speter      int slen;
625251881Speter
626251881Speter      if (option_codes)
627251881Speter        {
628251881Speter          if (! *option_codes)
629251881Speter            break;
630251881Speter          opt = find_option(options, *option_codes++);
631251881Speter        }
632251881Speter      else
633251881Speter        {
634251881Speter          opt = options++;
635251881Speter          if (! opt->code)
636251881Speter            break;
637251881Speter        }
638251881Speter
639251881Speter      if (! first)
640251881Speter        result = apr_pstrcat(pool, result, ",", (char *)NULL);
641251881Speter      s = apr_psprintf(pool, _(" (%s) %s"),
642251881Speter                       opt->code, _(opt->short_desc));
643251881Speter      slen = svn_utf_cstring_utf8_width(s);
644251881Speter      /* Break the line if adding the next option would make it too long */
645251881Speter      if (this_line_len + slen > MAX_PROMPT_WIDTH)
646251881Speter        {
647251881Speter          result = apr_pstrcat(pool, result, line_sep, (char *)NULL);
648251881Speter          this_line_len = left_margin;
649251881Speter        }
650251881Speter      result = apr_pstrcat(pool, result, s, (char *)NULL);
651251881Speter      this_line_len += slen;
652251881Speter      first = FALSE;
653251881Speter    }
654251881Speter  return apr_pstrcat(pool, result, ": ", (char *)NULL);
655251881Speter}
656251881Speter
657251881Speter/* Return a help string listing the OPTIONS. */
658251881Speterstatic const char *
659251881Speterhelp_string(const resolver_option_t *options,
660251881Speter            apr_pool_t *pool)
661251881Speter{
662251881Speter  const char *result = "";
663251881Speter  const resolver_option_t *opt;
664251881Speter
665251881Speter  for (opt = options; opt->code; opt++)
666251881Speter    {
667251881Speter      /* Append a line describing OPT, or a blank line if its code is "". */
668251881Speter      if (opt->code[0])
669251881Speter        {
670251881Speter          const char *s = apr_psprintf(pool, "  (%s)", opt->code);
671251881Speter
672251881Speter          result = apr_psprintf(pool, "%s%-6s - %s\n",
673251881Speter                                result, s, _(opt->long_desc));
674251881Speter        }
675251881Speter      else
676251881Speter        {
677251881Speter          result = apr_pstrcat(pool, result, "\n", (char *)NULL);
678251881Speter        }
679251881Speter    }
680251881Speter  result = apr_pstrcat(pool, result,
681251881Speter                       _("Words in square brackets are the corresponding "
682251881Speter                         "--accept option arguments.\n"),
683251881Speter                       (char *)NULL);
684251881Speter  return result;
685251881Speter}
686251881Speter
687251881Speter/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
688251881Speter * in OPTIONS_TO_SHOW if that is non-null.  Set *OPT to point to the chosen
689251881Speter * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
690251881Speter * NULL if the answer was not one of them.
691251881Speter *
692251881Speter * If the answer is the (globally recognized) 'help' option, then display
693251881Speter * the help (on stderr) and return with *OPT == NULL.
694251881Speter */
695251881Speterstatic svn_error_t *
696251881Speterprompt_user(const resolver_option_t **opt,
697251881Speter            const resolver_option_t *conflict_options,
698251881Speter            const char *const *options_to_show,
699251881Speter            void *prompt_baton,
700251881Speter            apr_pool_t *scratch_pool)
701251881Speter{
702251881Speter  const char *prompt
703251881Speter    = prompt_string(conflict_options, options_to_show, scratch_pool);
704251881Speter  const char *answer;
705251881Speter
706251881Speter  SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
707251881Speter  if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
708251881Speter    {
709251881Speter      SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
710251881Speter                                  help_string(conflict_options,
711251881Speter                                              scratch_pool)));
712251881Speter      *opt = NULL;
713251881Speter    }
714251881Speter  else
715251881Speter    {
716251881Speter      *opt = find_option(conflict_options, answer);
717251881Speter      if (! *opt)
718251881Speter        {
719251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
720251881Speter                                      _("Unrecognized option.\n\n")));
721251881Speter        }
722251881Speter    }
723251881Speter  return SVN_NO_ERROR;
724251881Speter}
725251881Speter
726251881Speter/* Ask the user what to do about the text conflict described by DESC.
727251881Speter * Return the answer in RESULT. B is the conflict baton for this
728251881Speter * conflict resolution session.
729251881Speter * SCRATCH_POOL is used for temporary allocations. */
730251881Speterstatic svn_error_t *
731251881Speterhandle_text_conflict(svn_wc_conflict_result_t *result,
732251881Speter                     const svn_wc_conflict_description2_t *desc,
733251881Speter                     svn_cl__interactive_conflict_baton_t *b,
734251881Speter                     apr_pool_t *scratch_pool)
735251881Speter{
736251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
737251881Speter  svn_boolean_t diff_allowed = FALSE;
738251881Speter  /* Have they done something that might have affected the merged
739251881Speter     file (so that we need to save a .edited copy)? */
740251881Speter  svn_boolean_t performed_edit = FALSE;
741251881Speter  /* Have they done *something* (edit, look at diff, etc) to
742251881Speter     give them a rational basis for choosing (r)esolved? */
743251881Speter  svn_boolean_t knows_something = FALSE;
744251881Speter
745251881Speter  SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text);
746251881Speter
747251881Speter  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
748251881Speter                              _("Conflict discovered in file '%s'.\n"),
749251881Speter                              svn_cl__local_style_skip_ancestor(
750251881Speter                                b->path_prefix, desc->local_abspath,
751251881Speter                                scratch_pool)));
752251881Speter
753251881Speter  /* Diffing can happen between base and merged, to show conflict
754251881Speter     markers to the user (this is the typical 3-way merge
755251881Speter     scenario), or if no base is available, we can show a diff
756251881Speter     between mine and theirs. */
757251881Speter  if ((desc->merged_file && desc->base_abspath)
758251881Speter      || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
759251881Speter    diff_allowed = TRUE;
760251881Speter
761251881Speter  while (TRUE)
762251881Speter    {
763251881Speter      const char *options[ARRAY_LEN(text_conflict_options)];
764251881Speter      const char **next_option = options;
765251881Speter      const resolver_option_t *opt;
766251881Speter
767251881Speter      svn_pool_clear(iterpool);
768251881Speter
769251881Speter      *next_option++ = "p";
770251881Speter      if (diff_allowed)
771251881Speter        {
772251881Speter          *next_option++ = "df";
773251881Speter          *next_option++ = "e";
774251881Speter          *next_option++ = "m";
775251881Speter
776251881Speter          if (knows_something)
777251881Speter            *next_option++ = "r";
778251881Speter
779251881Speter          if (! desc->is_binary)
780251881Speter            {
781251881Speter              *next_option++ = "mc";
782251881Speter              *next_option++ = "tc";
783251881Speter            }
784251881Speter        }
785251881Speter      else
786251881Speter        {
787251881Speter          if (knows_something)
788251881Speter            *next_option++ = "r";
789251881Speter          *next_option++ = "mf";
790251881Speter          *next_option++ = "tf";
791251881Speter        }
792251881Speter      *next_option++ = "s";
793251881Speter      *next_option++ = NULL;
794251881Speter
795251881Speter      SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->pb,
796251881Speter                          iterpool));
797251881Speter      if (! opt)
798251881Speter        continue;
799251881Speter
800251881Speter      if (strcmp(opt->code, "q") == 0)
801251881Speter        {
802251881Speter          result->choice = opt->choice;
803251881Speter          b->accept_which = svn_cl__accept_postpone;
804251881Speter          b->quit = TRUE;
805251881Speter          break;
806251881Speter        }
807251881Speter      else if (strcmp(opt->code, "s") == 0)
808251881Speter        {
809251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
810251881Speter                                      help_string(text_conflict_options,
811251881Speter                                                  iterpool)));
812251881Speter        }
813251881Speter      else if (strcmp(opt->code, "dc") == 0)
814251881Speter        {
815251881Speter          if (desc->is_binary)
816251881Speter            {
817251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
818251881Speter                                          _("Invalid option; cannot "
819251881Speter                                            "display conflicts for a "
820251881Speter                                            "binary file.\n\n")));
821251881Speter              continue;
822251881Speter            }
823251881Speter          else if (! (desc->my_abspath && desc->base_abspath &&
824251881Speter                      desc->their_abspath))
825251881Speter            {
826251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
827251881Speter                                          _("Invalid option; original "
828251881Speter                                            "files not available.\n\n")));
829251881Speter              continue;
830251881Speter            }
831251881Speter          SVN_ERR(show_conflicts(desc, iterpool));
832251881Speter          knows_something = TRUE;
833251881Speter        }
834251881Speter      else if (strcmp(opt->code, "df") == 0)
835251881Speter        {
836251881Speter          if (! diff_allowed)
837251881Speter            {
838251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
839251881Speter                             _("Invalid option; there's no "
840251881Speter                                "merged version to diff.\n\n")));
841251881Speter              continue;
842251881Speter            }
843251881Speter
844251881Speter          SVN_ERR(show_diff(desc, b->path_prefix, iterpool));
845251881Speter          knows_something = TRUE;
846251881Speter        }
847251881Speter      else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
848251881Speter        {
849251881Speter          SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool));
850251881Speter          if (performed_edit)
851251881Speter            knows_something = TRUE;
852251881Speter        }
853251881Speter      else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
854251881Speter               strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
855251881Speter        {
856251881Speter          if (desc->kind != svn_wc_conflict_kind_text)
857251881Speter            {
858251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
859251881Speter                                          _("Invalid option; can only "
860251881Speter                                            "resolve text conflicts with "
861251881Speter                                            "the internal merge tool."
862251881Speter                                            "\n\n")));
863251881Speter              continue;
864251881Speter            }
865251881Speter
866251881Speter          if (desc->base_abspath && desc->their_abspath &&
867251881Speter              desc->my_abspath && desc->merged_file)
868251881Speter            {
869251881Speter              svn_boolean_t remains_in_conflict;
870251881Speter
871251881Speter              SVN_ERR(svn_cl__merge_file(desc->base_abspath,
872251881Speter                                         desc->their_abspath,
873251881Speter                                         desc->my_abspath,
874251881Speter                                         desc->merged_file,
875251881Speter                                         desc->local_abspath,
876251881Speter                                         b->path_prefix,
877251881Speter                                         b->editor_cmd,
878251881Speter                                         b->config,
879251881Speter                                         &remains_in_conflict,
880251881Speter                                         iterpool));
881251881Speter              knows_something = !remains_in_conflict;
882251881Speter            }
883251881Speter          else
884251881Speter            SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
885251881Speter                                        _("Invalid option.\n\n")));
886251881Speter        }
887251881Speter      else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
888251881Speter        {
889251881Speter          /* ### This check should be earlier as it's nasty to offer an option
890251881Speter           *     and then when the user chooses it say 'Invalid option'. */
891251881Speter          /* ### 'merged_file' shouldn't be necessary *before* we launch the
892251881Speter           *     resolver: it should be the *result* of doing so. */
893251881Speter          if (desc->base_abspath && desc->their_abspath &&
894251881Speter              desc->my_abspath && desc->merged_file)
895251881Speter            {
896251881Speter              SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool));
897251881Speter              if (performed_edit)
898251881Speter                knows_something = TRUE;
899251881Speter            }
900251881Speter          else
901251881Speter            SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
902251881Speter                                        _("Invalid option.\n\n")));
903251881Speter        }
904251881Speter      else if (opt->choice != -1)
905251881Speter        {
906251881Speter          if ((opt->choice == svn_wc_conflict_choose_mine_conflict
907251881Speter               || opt->choice == svn_wc_conflict_choose_theirs_conflict)
908251881Speter              && desc->is_binary)
909251881Speter            {
910251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
911251881Speter                                          _("Invalid option; cannot choose "
912251881Speter                                            "based on conflicts in a "
913251881Speter                                            "binary file.\n\n")));
914251881Speter              continue;
915251881Speter            }
916251881Speter
917251881Speter          /* We only allow the user accept the merged version of
918251881Speter             the file if they've edited it, or at least looked at
919251881Speter             the diff. */
920269847Speter          if (opt->choice == svn_wc_conflict_choose_merged
921251881Speter              && ! knows_something)
922251881Speter            {
923251881Speter              SVN_ERR(svn_cmdline_fprintf(
924251881Speter                        stderr, iterpool,
925251881Speter                        _("Invalid option; use diff/edit/merge/launch "
926253734Speter                          "before choosing 'mark resolved'.\n\n")));
927251881Speter              continue;
928251881Speter            }
929251881Speter
930251881Speter          result->choice = opt->choice;
931251881Speter          if (performed_edit)
932251881Speter            result->save_merged = TRUE;
933251881Speter          break;
934251881Speter        }
935251881Speter    }
936251881Speter  svn_pool_destroy(iterpool);
937251881Speter
938251881Speter  return SVN_NO_ERROR;
939251881Speter}
940251881Speter
941251881Speter/* Ask the user what to do about the property conflict described by DESC.
942251881Speter * Return the answer in RESULT. B is the conflict baton for this
943251881Speter * conflict resolution session.
944251881Speter * SCRATCH_POOL is used for temporary allocations. */
945251881Speterstatic svn_error_t *
946251881Speterhandle_prop_conflict(svn_wc_conflict_result_t *result,
947251881Speter                     const svn_wc_conflict_description2_t *desc,
948251881Speter                     svn_cl__interactive_conflict_baton_t *b,
949251881Speter                     apr_pool_t *result_pool,
950251881Speter                     apr_pool_t *scratch_pool)
951251881Speter{
952251881Speter  apr_pool_t *iterpool;
953251881Speter  const char *message;
954251881Speter  const char *merged_file_path = NULL;
955251881Speter  svn_boolean_t resolved_allowed = FALSE;
956251881Speter
957251881Speter  /* ### Work around a historical bug in the provider: the path to the
958251881Speter   *     conflict description file was put in the 'theirs' field, and
959251881Speter   *     'theirs' was put in the 'merged' field. */
960251881Speter  ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file;
961251881Speter  ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL;
962251881Speter
963251881Speter  SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property);
964251881Speter
965251881Speter  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
966251881Speter                              _("Conflict for property '%s' discovered"
967251881Speter                                " on '%s'.\n"),
968251881Speter                              desc->property_name,
969251881Speter                              svn_cl__local_style_skip_ancestor(
970251881Speter                                b->path_prefix, desc->local_abspath,
971251881Speter                                scratch_pool)));
972251881Speter
973251881Speter  SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc,
974251881Speter                                                               scratch_pool));
975251881Speter  SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message));
976251881Speter
977251881Speter  iterpool = svn_pool_create(scratch_pool);
978251881Speter  while (TRUE)
979251881Speter    {
980251881Speter      const resolver_option_t *opt;
981251881Speter      const char *options[ARRAY_LEN(prop_conflict_options)];
982251881Speter      const char **next_option = options;
983251881Speter
984251881Speter      *next_option++ = "p";
985251881Speter      *next_option++ = "mf";
986251881Speter      *next_option++ = "tf";
987251881Speter      *next_option++ = "dc";
988251881Speter      *next_option++ = "e";
989251881Speter      if (resolved_allowed)
990251881Speter        *next_option++ = "r";
991251881Speter      *next_option++ = "q";
992251881Speter      *next_option++ = "h";
993251881Speter      *next_option++ = NULL;
994251881Speter
995251881Speter      svn_pool_clear(iterpool);
996251881Speter
997251881Speter      SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb,
998251881Speter                          iterpool));
999251881Speter      if (! opt)
1000251881Speter        continue;
1001251881Speter
1002251881Speter      if (strcmp(opt->code, "q") == 0)
1003251881Speter        {
1004251881Speter          result->choice = opt->choice;
1005251881Speter          b->accept_which = svn_cl__accept_postpone;
1006251881Speter          b->quit = TRUE;
1007251881Speter          break;
1008251881Speter        }
1009251881Speter      else if (strcmp(opt->code, "dc") == 0)
1010251881Speter        {
1011251881Speter          SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool));
1012251881Speter        }
1013251881Speter      else if (strcmp(opt->code, "e") == 0)
1014251881Speter        {
1015251881Speter          SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b,
1016251881Speter                                     result_pool, scratch_pool));
1017251881Speter          resolved_allowed = (merged_file_path != NULL);
1018251881Speter        }
1019251881Speter      else if (strcmp(opt->code, "r") == 0)
1020251881Speter        {
1021251881Speter          if (! resolved_allowed)
1022251881Speter            {
1023251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
1024251881Speter                             _("Invalid option; please edit the property "
1025251881Speter                               "first.\n\n")));
1026251881Speter              continue;
1027251881Speter            }
1028251881Speter
1029251881Speter          result->merged_file = merged_file_path;
1030251881Speter          result->choice = svn_wc_conflict_choose_merged;
1031251881Speter          break;
1032251881Speter        }
1033251881Speter      else if (opt->choice != -1)
1034251881Speter        {
1035251881Speter          result->choice = opt->choice;
1036251881Speter          break;
1037251881Speter        }
1038251881Speter    }
1039251881Speter  svn_pool_destroy(iterpool);
1040251881Speter
1041251881Speter  return SVN_NO_ERROR;
1042251881Speter}
1043251881Speter
1044251881Speter/* Ask the user what to do about the tree conflict described by DESC.
1045251881Speter * Return the answer in RESULT. B is the conflict baton for this
1046251881Speter * conflict resolution session.
1047251881Speter * SCRATCH_POOL is used for temporary allocations. */
1048251881Speterstatic svn_error_t *
1049251881Speterhandle_tree_conflict(svn_wc_conflict_result_t *result,
1050251881Speter                     const svn_wc_conflict_description2_t *desc,
1051251881Speter                     svn_cl__interactive_conflict_baton_t *b,
1052251881Speter                     apr_pool_t *scratch_pool)
1053251881Speter{
1054251881Speter  const char *readable_desc;
1055251881Speter  apr_pool_t *iterpool;
1056251881Speter
1057251881Speter  SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
1058251881Speter           &readable_desc, desc, scratch_pool));
1059251881Speter  SVN_ERR(svn_cmdline_fprintf(
1060251881Speter               stderr, scratch_pool,
1061251881Speter               _("Tree conflict on '%s'\n   > %s\n"),
1062251881Speter               svn_cl__local_style_skip_ancestor(b->path_prefix,
1063251881Speter                                                 desc->local_abspath,
1064251881Speter                                                 scratch_pool),
1065251881Speter               readable_desc));
1066251881Speter
1067251881Speter  iterpool = svn_pool_create(scratch_pool);
1068251881Speter  while (1)
1069251881Speter    {
1070251881Speter      const resolver_option_t *opt;
1071251881Speter      const resolver_option_t *tc_opts;
1072251881Speter
1073251881Speter      svn_pool_clear(iterpool);
1074251881Speter
1075251881Speter      if (desc->operation == svn_wc_operation_update ||
1076251881Speter          desc->operation == svn_wc_operation_switch)
1077251881Speter        {
1078251881Speter          if (desc->reason == svn_wc_conflict_reason_moved_away)
1079253734Speter            {
1080253734Speter              if (desc->action == svn_wc_conflict_action_edit)
1081253734Speter                tc_opts = tree_conflict_options_update_edit_moved_away;
1082253734Speter              else
1083253734Speter                tc_opts = tree_conflict_options_update_moved_away;
1084253734Speter            }
1085251881Speter          else if (desc->reason == svn_wc_conflict_reason_deleted)
1086251881Speter            tc_opts = tree_conflict_options_update_deleted;
1087251881Speter          else if (desc->reason == svn_wc_conflict_reason_replaced)
1088251881Speter            tc_opts = tree_conflict_options_update_replaced;
1089251881Speter          else
1090251881Speter            tc_opts = tree_conflict_options;
1091251881Speter        }
1092251881Speter      else
1093251881Speter        tc_opts = tree_conflict_options;
1094251881Speter
1095251881Speter      SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool));
1096251881Speter      if (! opt)
1097251881Speter        continue;
1098251881Speter
1099251881Speter      if (strcmp(opt->code, "q") == 0)
1100251881Speter        {
1101251881Speter          result->choice = opt->choice;
1102251881Speter          b->accept_which = svn_cl__accept_postpone;
1103251881Speter          b->quit = TRUE;
1104251881Speter          break;
1105251881Speter        }
1106251881Speter      else if (opt->choice != -1)
1107251881Speter        {
1108251881Speter          result->choice = opt->choice;
1109251881Speter          break;
1110251881Speter        }
1111251881Speter    }
1112251881Speter  svn_pool_destroy(iterpool);
1113251881Speter
1114251881Speter  return SVN_NO_ERROR;
1115251881Speter}
1116251881Speter
1117251881Speter/* The body of svn_cl__conflict_func_interactive(). */
1118251881Speterstatic svn_error_t *
1119251881Speterconflict_func_interactive(svn_wc_conflict_result_t **result,
1120251881Speter                          const svn_wc_conflict_description2_t *desc,
1121251881Speter                          void *baton,
1122251881Speter                          apr_pool_t *result_pool,
1123251881Speter                          apr_pool_t *scratch_pool)
1124251881Speter{
1125251881Speter  svn_cl__interactive_conflict_baton_t *b = baton;
1126251881Speter  svn_error_t *err;
1127251881Speter
1128251881Speter  /* Start out assuming we're going to postpone the conflict. */
1129251881Speter  *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
1130251881Speter                                          NULL, result_pool);
1131251881Speter
1132251881Speter  switch (b->accept_which)
1133251881Speter    {
1134251881Speter    case svn_cl__accept_invalid:
1135251881Speter    case svn_cl__accept_unspecified:
1136251881Speter      /* No (or no valid) --accept option, fall through to prompting. */
1137251881Speter      break;
1138251881Speter    case svn_cl__accept_postpone:
1139251881Speter      (*result)->choice = svn_wc_conflict_choose_postpone;
1140251881Speter      return SVN_NO_ERROR;
1141251881Speter    case svn_cl__accept_base:
1142251881Speter      (*result)->choice = svn_wc_conflict_choose_base;
1143251881Speter      return SVN_NO_ERROR;
1144251881Speter    case svn_cl__accept_working:
1145251881Speter      /* If the caller didn't merge the property values, then I guess
1146251881Speter       * 'choose working' means 'choose mine'... */
1147251881Speter      if (! desc->merged_file)
1148251881Speter        (*result)->merged_file = desc->my_abspath;
1149251881Speter      (*result)->choice = svn_wc_conflict_choose_merged;
1150251881Speter      return SVN_NO_ERROR;
1151251881Speter    case svn_cl__accept_mine_conflict:
1152251881Speter      (*result)->choice = svn_wc_conflict_choose_mine_conflict;
1153251881Speter      return SVN_NO_ERROR;
1154251881Speter    case svn_cl__accept_theirs_conflict:
1155251881Speter      (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
1156251881Speter      return SVN_NO_ERROR;
1157251881Speter    case svn_cl__accept_mine_full:
1158251881Speter      (*result)->choice = svn_wc_conflict_choose_mine_full;
1159251881Speter      return SVN_NO_ERROR;
1160251881Speter    case svn_cl__accept_theirs_full:
1161251881Speter      (*result)->choice = svn_wc_conflict_choose_theirs_full;
1162251881Speter      return SVN_NO_ERROR;
1163251881Speter    case svn_cl__accept_edit:
1164251881Speter      if (desc->merged_file)
1165251881Speter        {
1166251881Speter          if (b->external_failed)
1167251881Speter            {
1168251881Speter              (*result)->choice = svn_wc_conflict_choose_postpone;
1169251881Speter              return SVN_NO_ERROR;
1170251881Speter            }
1171251881Speter
1172251881Speter          err = svn_cmdline__edit_file_externally(desc->merged_file,
1173251881Speter                                                  b->editor_cmd, b->config,
1174251881Speter                                                  scratch_pool);
1175251881Speter          if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
1176251881Speter            {
1177251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1178251881Speter                                          err->message ? err->message :
1179251881Speter                                          _("No editor found;"
1180251881Speter                                            " leaving all conflicts.")));
1181251881Speter              svn_error_clear(err);
1182251881Speter              b->external_failed = TRUE;
1183251881Speter            }
1184251881Speter          else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
1185251881Speter            {
1186251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1187251881Speter                                          err->message ? err->message :
1188251881Speter                                          _("Error running editor;"
1189251881Speter                                            " leaving all conflicts.")));
1190251881Speter              svn_error_clear(err);
1191251881Speter              b->external_failed = TRUE;
1192251881Speter            }
1193251881Speter          else if (err)
1194251881Speter            return svn_error_trace(err);
1195251881Speter          (*result)->choice = svn_wc_conflict_choose_merged;
1196251881Speter          return SVN_NO_ERROR;
1197251881Speter        }
1198251881Speter      /* else, fall through to prompting. */
1199251881Speter      break;
1200251881Speter    case svn_cl__accept_launch:
1201251881Speter      if (desc->base_abspath && desc->their_abspath
1202251881Speter          && desc->my_abspath && desc->merged_file)
1203251881Speter        {
1204251881Speter          svn_boolean_t remains_in_conflict;
1205251881Speter
1206251881Speter          if (b->external_failed)
1207251881Speter            {
1208251881Speter              (*result)->choice = svn_wc_conflict_choose_postpone;
1209251881Speter              return SVN_NO_ERROR;
1210251881Speter            }
1211251881Speter
1212251881Speter          err = svn_cl__merge_file_externally(desc->base_abspath,
1213251881Speter                                              desc->their_abspath,
1214251881Speter                                              desc->my_abspath,
1215251881Speter                                              desc->merged_file,
1216251881Speter                                              desc->local_abspath,
1217251881Speter                                              b->config,
1218251881Speter                                              &remains_in_conflict,
1219251881Speter                                              scratch_pool);
1220251881Speter          if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
1221251881Speter            {
1222251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1223251881Speter                                          err->message ? err->message :
1224251881Speter                                          _("No merge tool found;"
1225251881Speter                                            " leaving all conflicts.")));
1226251881Speter              b->external_failed = TRUE;
1227251881Speter              return svn_error_trace(err);
1228251881Speter            }
1229251881Speter          else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
1230251881Speter            {
1231251881Speter              SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
1232251881Speter                                          err->message ? err->message :
1233251881Speter                                          _("Error running merge tool;"
1234251881Speter                                            " leaving all conflicts.")));
1235251881Speter              b->external_failed = TRUE;
1236251881Speter              return svn_error_trace(err);
1237251881Speter            }
1238251881Speter          else if (err)
1239251881Speter            return svn_error_trace(err);
1240251881Speter
1241251881Speter          if (remains_in_conflict)
1242251881Speter            (*result)->choice = svn_wc_conflict_choose_postpone;
1243251881Speter          else
1244251881Speter            (*result)->choice = svn_wc_conflict_choose_merged;
1245251881Speter          return SVN_NO_ERROR;
1246251881Speter        }
1247251881Speter      /* else, fall through to prompting. */
1248251881Speter      break;
1249251881Speter    }
1250251881Speter
1251251881Speter  /* We're in interactive mode and either the user gave no --accept
1252251881Speter     option or the option did not apply; let's prompt. */
1253251881Speter
1254251881Speter  /* Handle the most common cases, which is either:
1255251881Speter
1256251881Speter     Conflicting edits on a file's text, or
1257251881Speter     Conflicting edits on a property.
1258251881Speter  */
1259251881Speter  if (((desc->kind == svn_wc_conflict_kind_text)
1260251881Speter       && (desc->action == svn_wc_conflict_action_edit)
1261251881Speter       && (desc->reason == svn_wc_conflict_reason_edited)))
1262251881Speter    SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool));
1263251881Speter  else if (desc->kind == svn_wc_conflict_kind_property)
1264251881Speter    SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool));
1265251881Speter  else if (desc->kind == svn_wc_conflict_kind_tree)
1266251881Speter    SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool));
1267251881Speter
1268251881Speter  else /* other types of conflicts -- do nothing about them. */
1269251881Speter    {
1270251881Speter      (*result)->choice = svn_wc_conflict_choose_postpone;
1271251881Speter    }
1272251881Speter
1273251881Speter  return SVN_NO_ERROR;
1274251881Speter}
1275251881Speter
1276251881Spetersvn_error_t *
1277251881Spetersvn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
1278251881Speter                                  const svn_wc_conflict_description2_t *desc,
1279251881Speter                                  void *baton,
1280251881Speter                                  apr_pool_t *result_pool,
1281251881Speter                                  apr_pool_t *scratch_pool)
1282251881Speter{
1283251881Speter  svn_cl__interactive_conflict_baton_t *b = baton;
1284251881Speter
1285251881Speter  SVN_ERR(conflict_func_interactive(result, desc, baton,
1286251881Speter                                    result_pool, scratch_pool));
1287251881Speter
1288251881Speter  /* If we are resolving a conflict, adjust the summary of conflicts. */
1289251881Speter  if ((*result)->choice != svn_wc_conflict_choose_postpone)
1290251881Speter    {
1291251881Speter      const char *local_path
1292251881Speter        = svn_cl__local_style_skip_ancestor(
1293251881Speter            b->path_prefix, desc->local_abspath, scratch_pool);
1294251881Speter
1295251881Speter      svn_cl__conflict_stats_resolved(b->conflict_stats, local_path,
1296251881Speter                                             desc->kind);
1297251881Speter    }
1298251881Speter  return SVN_NO_ERROR;
1299251881Speter}
1300