1/*
2 * file-merge.c: internal file merge tool
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* This is an interactive file merge tool with an interface similar to
25 * the interactive mode of the UNIX sdiff ("side-by-side diff") utility.
26 * The merge tool is driven by Subversion's diff code and user input. */
27
28#include "svn_cmdline.h"
29#include "svn_dirent_uri.h"
30#include "svn_error.h"
31#include "svn_pools.h"
32#include "svn_io.h"
33#include "svn_utf.h"
34#include "svn_xml.h"
35
36#include "cl.h"
37
38#include "svn_private_config.h"
39#include "private/svn_utf_private.h"
40#include "private/svn_cmdline_private.h"
41#include "private/svn_dep_compat.h"
42
43#if APR_HAVE_SYS_IOCTL_H
44#include <sys/ioctl.h>
45#endif
46
47#if APR_HAVE_UNISTD_H
48#include <unistd.h>
49#endif
50
51#include <fcntl.h>
52#include <stdlib.h>
53
54#if defined(HAVE_TERMIOS_H)
55#include <termios.h>
56#endif
57
58/* Baton for functions in this file which implement svn_diff_output_fns_t. */
59struct file_merge_baton {
60  /* The files being merged. */
61  apr_file_t *original_file;
62  apr_file_t *modified_file;
63  apr_file_t *latest_file;
64
65  /* Counters to keep track of the current line in each file. */
66  svn_linenum_t current_line_original;
67  svn_linenum_t current_line_modified;
68  svn_linenum_t current_line_latest;
69
70  /* The merge result is written to this file. */
71  apr_file_t *merged_file;
72
73  /* Whether the merged file remains in conflict after the merge. */
74  svn_boolean_t remains_in_conflict;
75
76  /* External editor command for editing chunks. */
77  const char *editor_cmd;
78
79  /* The client configuration hash. */
80  apr_hash_t *config;
81
82  /* Whether the merge should be aborted. */
83  svn_boolean_t abort_merge;
84
85  /* Pool for temporary allocations. */
86  apr_pool_t *scratch_pool;
87};
88
89/* Copy LEN lines from SOURCE_FILE to the MERGED_FILE, starting at
90 * line START. The CURRENT_LINE is the current line in the source file.
91 * The new current line is returned in *NEW_CURRENT_LINE. */
92static svn_error_t *
93copy_to_merged_file(svn_linenum_t *new_current_line,
94                    apr_file_t *merged_file,
95                    apr_file_t *source_file,
96                    apr_off_t start,
97                    apr_off_t len,
98                    svn_linenum_t current_line,
99                    apr_pool_t *scratch_pool)
100{
101  apr_pool_t *iterpool;
102  svn_stringbuf_t *line;
103  apr_size_t lines_read;
104  apr_size_t lines_copied;
105  svn_boolean_t eof;
106  svn_linenum_t orig_current_line = current_line;
107
108  lines_read = 0;
109  iterpool = svn_pool_create(scratch_pool);
110  while (current_line < start)
111    {
112      svn_pool_clear(iterpool);
113
114      SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof,
115                                   APR_SIZE_MAX, iterpool, iterpool));
116      if (eof)
117        break;
118
119      current_line++;
120      lines_read++;
121    }
122
123  lines_copied = 0;
124  while (lines_copied < len)
125    {
126      apr_size_t bytes_written;
127      const char *eol_str;
128
129      svn_pool_clear(iterpool);
130
131      SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof,
132                                   APR_SIZE_MAX, iterpool, iterpool));
133      if (eol_str)
134        svn_stringbuf_appendcstr(line, eol_str);
135      SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
136                                     &bytes_written, iterpool));
137      if (bytes_written != line->len)
138        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
139                                _("Could not write data to merged file"));
140      if (eof)
141        break;
142      lines_copied++;
143    }
144  svn_pool_destroy(iterpool);
145
146  *new_current_line = orig_current_line + lines_read + lines_copied;
147
148  return SVN_NO_ERROR;
149}
150
151/* Copy common data to the merged file. */
152static svn_error_t *
153file_merge_output_common(void *output_baton,
154                         apr_off_t original_start,
155                         apr_off_t original_length,
156                         apr_off_t modified_start,
157                         apr_off_t modified_length,
158                         apr_off_t latest_start,
159                         apr_off_t latest_length)
160{
161  struct file_merge_baton *b = output_baton;
162
163  if (b->abort_merge)
164    return SVN_NO_ERROR;
165
166  SVN_ERR(copy_to_merged_file(&b->current_line_original,
167                              b->merged_file,
168                              b->original_file,
169                              original_start,
170                              original_length,
171                              b->current_line_original,
172                              b->scratch_pool));
173  return SVN_NO_ERROR;
174}
175
176/* Original/latest match up, but modified differs.
177 * Copy modified data to the merged file. */
178static svn_error_t *
179file_merge_output_diff_modified(void *output_baton,
180                                apr_off_t original_start,
181                                apr_off_t original_length,
182                                apr_off_t modified_start,
183                                apr_off_t modified_length,
184                                apr_off_t latest_start,
185                                apr_off_t latest_length)
186{
187  struct file_merge_baton *b = output_baton;
188
189  if (b->abort_merge)
190    return SVN_NO_ERROR;
191
192  SVN_ERR(copy_to_merged_file(&b->current_line_modified,
193                              b->merged_file,
194                              b->modified_file,
195                              modified_start,
196                              modified_length,
197                              b->current_line_modified,
198                              b->scratch_pool));
199
200  return SVN_NO_ERROR;
201}
202
203/* Original/modified match up, but latest differs.
204 * Copy latest data to the merged file. */
205static svn_error_t *
206file_merge_output_diff_latest(void *output_baton,
207                              apr_off_t original_start,
208                              apr_off_t original_length,
209                              apr_off_t modified_start,
210                              apr_off_t modified_length,
211                              apr_off_t latest_start,
212                              apr_off_t latest_length)
213{
214  struct file_merge_baton *b = output_baton;
215
216  if (b->abort_merge)
217    return SVN_NO_ERROR;
218
219  SVN_ERR(copy_to_merged_file(&b->current_line_latest,
220                              b->merged_file,
221                              b->latest_file,
222                              latest_start,
223                              latest_length,
224                              b->current_line_latest,
225                              b->scratch_pool));
226
227  return SVN_NO_ERROR;
228}
229
230/* Modified/latest match up, but original differs.
231 * Copy latest data to the merged file. */
232static svn_error_t *
233file_merge_output_diff_common(void *output_baton,
234                              apr_off_t original_start,
235                              apr_off_t original_length,
236                              apr_off_t modified_start,
237                              apr_off_t modified_length,
238                              apr_off_t latest_start,
239                              apr_off_t latest_length)
240{
241  struct file_merge_baton *b = output_baton;
242
243  if (b->abort_merge)
244    return SVN_NO_ERROR;
245
246  SVN_ERR(copy_to_merged_file(&b->current_line_latest,
247                              b->merged_file,
248                              b->latest_file,
249                              latest_start,
250                              latest_length,
251                              b->current_line_latest,
252                              b->scratch_pool));
253  return SVN_NO_ERROR;
254}
255
256
257/* Return LEN lines within the diff chunk staring at line START
258 * in a *LINES array of svn_stringbuf_t* elements.
259 * Store the resulting current in in *NEW_CURRENT_LINE. */
260static svn_error_t *
261read_diff_chunk(apr_array_header_t **lines,
262                svn_linenum_t *new_current_line,
263                apr_file_t *file,
264                svn_linenum_t current_line,
265                apr_off_t start,
266                apr_off_t len,
267                apr_pool_t *result_pool,
268                apr_pool_t *scratch_pool)
269{
270  svn_stringbuf_t *line;
271  const char *eol_str;
272  svn_boolean_t eof;
273  apr_pool_t *iterpool;
274
275  *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
276
277  /* Skip lines before start of range. */
278  iterpool = svn_pool_create(scratch_pool);
279  while (current_line < start)
280    {
281      svn_pool_clear(iterpool);
282      SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX,
283                                   iterpool, iterpool));
284      if (eof)
285        return SVN_NO_ERROR;
286      current_line++;
287    }
288  svn_pool_destroy(iterpool);
289
290  /* Now read the lines. */
291  do
292    {
293      SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX,
294                                   result_pool, scratch_pool));
295      if (eol_str)
296        svn_stringbuf_appendcstr(line, eol_str);
297      APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line;
298      if (eof)
299        break;
300      current_line++;
301    }
302  while ((*lines)->nelts < len);
303
304  *new_current_line = current_line;
305
306  return SVN_NO_ERROR;
307}
308
309/* Return the terminal width in number of columns. */
310static int
311get_term_width(void)
312{
313  char *columns_env;
314#ifdef TIOCGWINSZ
315  int fd;
316
317  fd = open("/dev/tty", O_RDONLY, 0);
318  if (fd != -1)
319    {
320      struct winsize ws;
321      int error;
322
323      error = ioctl(fd, TIOCGWINSZ, &ws);
324      close(fd);
325      if (error != -1)
326        {
327          if (ws.ws_col < 80)
328            return 80;
329          return ws.ws_col;
330        }
331    }
332#elif defined WIN32
333  CONSOLE_SCREEN_BUFFER_INFO csbi;
334
335  if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
336    {
337      if (csbi.dwSize.X < 80)
338        return 80;
339      return csbi.dwSize.X;
340    }
341#endif
342
343  columns_env = getenv("COLUMNS");
344  if (columns_env)
345    {
346      svn_error_t *err;
347      int cols;
348
349      err = svn_cstring_atoi(&cols, columns_env);
350      if (err)
351        {
352          svn_error_clear(err);
353          return 80;
354        }
355
356      if (cols < 80)
357        return 80;
358      return cols;
359    }
360  else
361    return 80;
362}
363
364#define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2)
365
366/* Prepare LINE for display, pruning or extending it to an appropriate
367 * display width, and stripping the EOL marker, if any.
368 * This function assumes that the data in LINE is encoded in UTF-8. */
369static const char *
370prepare_line_for_display(const char *line, apr_pool_t *pool)
371{
372  svn_stringbuf_t *buf = svn_stringbuf_create(line, pool);
373  size_t width;
374  size_t line_width = LINE_DISPLAY_WIDTH;
375  apr_pool_t *iterpool;
376
377  /* Trim EOL. */
378  if (buf->len >= 2 &&
379      buf->data[buf->len - 2] == '\r' &&
380      buf->data[buf->len - 1] == '\n')
381    svn_stringbuf_chop(buf, 2);
382  else if (buf->len >= 1 &&
383           (buf->data[buf->len - 1] == '\n' ||
384            buf->data[buf->len - 1] == '\r'))
385    svn_stringbuf_chop(buf, 1);
386
387  /* Determine the on-screen width of the line. */
388  width = svn_utf_cstring_utf8_width(buf->data);
389  if (width == -1)
390    {
391      /* Determining the width failed. Try to get rid of unprintable
392       * characters in the line buffer. */
393      buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool);
394      width = svn_utf_cstring_utf8_width(buf->data);
395      if (width == -1)
396        width = buf->len; /* fallback: buffer length */
397    }
398
399  /* Trim further in case line is still too long, or add padding in case
400   * it is too short. */
401  iterpool = svn_pool_create(pool);
402  while (width > line_width)
403    {
404      const char *last_valid;
405
406      svn_pool_clear(iterpool);
407
408      svn_stringbuf_chop(buf, 1);
409
410      /* Be careful not to invalidate the UTF-8 string by trimming
411       * just part of a character. */
412      last_valid = svn_utf__last_valid(buf->data, buf->len);
413      if (last_valid < buf->data + buf->len)
414        svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid);
415
416      width = svn_utf_cstring_utf8_width(buf->data);
417      if (width == -1)
418        width = buf->len; /* fallback: buffer length */
419    }
420  svn_pool_destroy(iterpool);
421
422  while (width == 0 || width < line_width)
423    {
424      svn_stringbuf_appendbyte(buf, ' ');
425      width++;
426    }
427
428  SVN_ERR_ASSERT_NO_RETURN(width == line_width);
429  return buf->data;
430}
431
432/* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */
433static apr_array_header_t *
434merge_chunks_with_conflict_markers(apr_array_header_t *chunk1,
435                                   apr_array_header_t *chunk2,
436                                   apr_pool_t *result_pool)
437{
438  apr_array_header_t *merged_chunk;
439  int i;
440
441  merged_chunk = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
442  /* ### would be nice to show filenames next to conflict markers */
443  APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
444    svn_stringbuf_create("<<<<<<<\n", result_pool);
445  for (i = 0; i < chunk1->nelts; i++)
446    {
447      APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
448        APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*);
449    }
450  APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
451    svn_stringbuf_create("=======\n", result_pool);
452  for (i = 0; i < chunk2->nelts; i++)
453    {
454      APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
455        APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*);
456    }
457  APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
458    svn_stringbuf_create(">>>>>>>\n", result_pool);
459
460  return merged_chunk;
461}
462
463/* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */
464static svn_error_t *
465edit_chunk(apr_array_header_t **merged_chunk,
466           apr_array_header_t *chunk,
467           const char *editor_cmd,
468           apr_hash_t *config,
469           apr_pool_t *result_pool,
470           apr_pool_t *scratch_pool)
471{
472  apr_file_t *temp_file;
473  const char *temp_file_name;
474  int i;
475  apr_off_t pos;
476  svn_boolean_t eof;
477  svn_error_t *err;
478  apr_pool_t *iterpool;
479
480  SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL,
481                                   svn_io_file_del_on_pool_cleanup,
482                                   scratch_pool, scratch_pool));
483  iterpool = svn_pool_create(scratch_pool);
484  for (i = 0; i < chunk->nelts; i++)
485    {
486      svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *);
487      apr_size_t bytes_written;
488
489      svn_pool_clear(iterpool);
490
491      SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len,
492                                     &bytes_written, iterpool));
493      if (line->len != bytes_written)
494        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
495                                _("Could not write data to temporary file"));
496    }
497  SVN_ERR(svn_io_file_flush(temp_file, scratch_pool));
498
499  err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd,
500                                          config, scratch_pool);
501  if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
502    {
503      svn_error_t *root_err = svn_error_root_cause(err);
504
505      SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
506                                  root_err->message ? root_err->message :
507                                  _("No editor found.")));
508      svn_error_clear(err);
509      *merged_chunk = NULL;
510      svn_pool_destroy(iterpool);
511      return SVN_NO_ERROR;
512    }
513  else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
514    {
515      svn_error_t *root_err = svn_error_root_cause(err);
516
517      SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
518                                  root_err->message ? root_err->message :
519                                  _("Error running editor.")));
520      svn_error_clear(err);
521      *merged_chunk = NULL;
522      svn_pool_destroy(iterpool);
523      return SVN_NO_ERROR;
524    }
525  else if (err)
526    return svn_error_trace(err);
527
528  *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *));
529  pos = 0;
530  SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool));
531  do
532    {
533      svn_stringbuf_t *line;
534      const char *eol_str;
535
536      svn_pool_clear(iterpool);
537
538      SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof,
539                                   APR_SIZE_MAX, result_pool, iterpool));
540      if (eol_str)
541        svn_stringbuf_appendcstr(line, eol_str);
542
543      APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line;
544    }
545  while (!eof);
546  svn_pool_destroy(iterpool);
547
548  SVN_ERR(svn_io_file_close(temp_file, scratch_pool));
549
550  return SVN_NO_ERROR;
551}
552
553/* Create a separator string of the appropriate length. */
554static const char *
555get_sep_string(apr_pool_t *result_pool)
556{
557  int line_width = LINE_DISPLAY_WIDTH;
558  int i;
559  svn_stringbuf_t *buf;
560
561  buf = svn_stringbuf_create_empty(result_pool);
562  for (i = 0; i < line_width; i++)
563    svn_stringbuf_appendbyte(buf, '-');
564  svn_stringbuf_appendbyte(buf, '+');
565  for (i = 0; i < line_width; i++)
566    svn_stringbuf_appendbyte(buf, '-');
567  svn_stringbuf_appendbyte(buf, '\n');
568
569  return buf->data;
570}
571
572/* Merge chunks CHUNK1 and CHUNK2.
573 * Each lines array contains elements of type svn_stringbuf_t*.
574 * Return the result in *MERGED_CHUNK, or set *MERGED_CHUNK to NULL in
575 * case the user chooses to postpone resolution of this chunk.
576 * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
577static svn_error_t *
578merge_chunks(apr_array_header_t **merged_chunk,
579             svn_boolean_t *abort_merge,
580             apr_array_header_t *chunk1,
581             apr_array_header_t *chunk2,
582             svn_linenum_t current_line1,
583             svn_linenum_t current_line2,
584             const char *editor_cmd,
585             apr_hash_t *config,
586             apr_pool_t *result_pool,
587             apr_pool_t *scratch_pool)
588{
589  svn_stringbuf_t *prompt;
590  int i;
591  int max_chunk_lines;
592  apr_pool_t *iterpool;
593
594  max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts
595                                                  : chunk2->nelts;
596  *abort_merge = FALSE;
597
598  /*
599   * Prepare the selection prompt.
600   */
601
602  prompt = svn_stringbuf_create(
603             apr_psprintf(scratch_pool, "%s\n%s|%s\n%s",
604                          _("Conflicting section found during merge:"),
605                          prepare_line_for_display(
606                            apr_psprintf(scratch_pool,
607                                         _("(1) their version (at line %lu)"),
608                                         current_line1),
609                            scratch_pool),
610                          prepare_line_for_display(
611                            apr_psprintf(scratch_pool,
612                                         _("(2) your version (at line %lu)"),
613                                         current_line2),
614                            scratch_pool),
615                          get_sep_string(scratch_pool)),
616             scratch_pool);
617
618  iterpool = svn_pool_create(scratch_pool);
619  for (i = 0; i < max_chunk_lines; i++)
620    {
621      const char *line1;
622      const char *line2;
623      const char *prompt_line;
624
625      svn_pool_clear(iterpool);
626
627      if (i < chunk1->nelts)
628        {
629          svn_stringbuf_t *line_utf8;
630
631          SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
632                                            APR_ARRAY_IDX(chunk1, i,
633                                                          svn_stringbuf_t*),
634                                            iterpool));
635          line1 = prepare_line_for_display(line_utf8->data, iterpool);
636        }
637      else
638        line1 = prepare_line_for_display("", iterpool);
639
640      if (i < chunk2->nelts)
641        {
642          svn_stringbuf_t *line_utf8;
643
644          SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
645                                            APR_ARRAY_IDX(chunk2, i,
646                                                          svn_stringbuf_t*),
647                                            iterpool));
648          line2 = prepare_line_for_display(line_utf8->data, iterpool);
649        }
650      else
651        line2 = prepare_line_for_display("", iterpool);
652
653      prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2);
654
655      svn_stringbuf_appendcstr(prompt, prompt_line);
656    }
657
658  svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool));
659  svn_stringbuf_appendcstr(
660    prompt,
661    _("Select: (1) use their version, (2) use your version,\n"
662      "        (12) their version first, then yours,\n"
663      "        (21) your version first, then theirs,\n"
664      "        (e1) edit their version and use the result,\n"
665      "        (e2) edit your version and use the result,\n"
666      "        (eb) edit both versions and use the result,\n"
667      "        (p) postpone this conflicting section leaving conflict markers,\n"
668      "        (a) abort file merge and return to main menu: "));
669
670  /* Now let's see what the user wants to do with this conflict. */
671  while (TRUE)
672    {
673      const char *answer;
674
675      svn_pool_clear(iterpool);
676
677      SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool));
678      if (strcmp(answer, "1") == 0)
679        {
680          *merged_chunk = chunk1;
681          break;
682        }
683      else if (strcmp(answer, "2") == 0)
684        {
685          *merged_chunk = chunk2;
686          break;
687        }
688      if (strcmp(answer, "12") == 0)
689        {
690          *merged_chunk = apr_array_make(result_pool,
691                                         chunk1->nelts + chunk2->nelts,
692                                         sizeof(svn_stringbuf_t *));
693          apr_array_cat(*merged_chunk, chunk1);
694          apr_array_cat(*merged_chunk, chunk2);
695          break;
696        }
697      if (strcmp(answer, "21") == 0)
698        {
699          *merged_chunk = apr_array_make(result_pool,
700                                         chunk1->nelts + chunk2->nelts,
701                                         sizeof(svn_stringbuf_t *));
702          apr_array_cat(*merged_chunk, chunk2);
703          apr_array_cat(*merged_chunk, chunk1);
704          break;
705        }
706      else if (strcmp(answer, "p") == 0)
707        {
708          *merged_chunk = NULL;
709          break;
710        }
711      else if (strcmp(answer, "e1") == 0)
712        {
713          SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config,
714                             result_pool, iterpool));
715          if (*merged_chunk)
716            break;
717        }
718      else if (strcmp(answer, "e2") == 0)
719        {
720          SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config,
721                             result_pool, iterpool));
722          if (*merged_chunk)
723            break;
724        }
725      else if (strcmp(answer, "eb") == 0)
726        {
727          apr_array_header_t *conflict_chunk;
728
729          conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
730                                                              scratch_pool);
731          SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config,
732                             result_pool, iterpool));
733          if (*merged_chunk)
734            break;
735        }
736      else if (strcmp(answer, "a") == 0)
737        {
738          *abort_merge = TRUE;
739          break;
740        }
741    }
742  svn_pool_destroy(iterpool);
743
744  return SVN_NO_ERROR;
745}
746
747/* Perform a merge of chunks from FILE1 and FILE2, specified by START1/LEN1
748 * and START2/LEN2, respectively. Append the result to MERGED_FILE.
749 * The current line numbers for FILE1 and FILE2 are passed in *CURRENT_LINE1
750 * and *CURRENT_LINE2, and will be updated to new values upon return.
751 * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
752static svn_error_t *
753merge_file_chunks(svn_boolean_t *remains_in_conflict,
754                  svn_boolean_t *abort_merge,
755                  apr_file_t *merged_file,
756                  apr_file_t *file1,
757                  apr_file_t *file2,
758                  apr_off_t start1,
759                  apr_off_t len1,
760                  apr_off_t start2,
761                  apr_off_t len2,
762                  svn_linenum_t *current_line1,
763                  svn_linenum_t *current_line2,
764                  const char *editor_cmd,
765                  apr_hash_t *config,
766                  apr_pool_t *scratch_pool)
767{
768  apr_array_header_t *chunk1;
769  apr_array_header_t *chunk2;
770  apr_array_header_t *merged_chunk;
771  apr_pool_t *iterpool;
772  int i;
773
774  SVN_ERR(read_diff_chunk(&chunk1, current_line1, file1, *current_line1,
775                          start1, len1, scratch_pool, scratch_pool));
776  SVN_ERR(read_diff_chunk(&chunk2, current_line2, file2, *current_line2,
777                          start2, len2, scratch_pool, scratch_pool));
778
779  SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2,
780                       *current_line1, *current_line2,
781                       editor_cmd, config,
782                       scratch_pool, scratch_pool));
783
784  if (*abort_merge)
785      return SVN_NO_ERROR;
786
787  /* If the user chose 'postpone' put conflict markers and left/right
788   * versions into the merged file. */
789  if (merged_chunk == NULL)
790    {
791      *remains_in_conflict = TRUE;
792      merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
793                                                        scratch_pool);
794    }
795
796  iterpool = svn_pool_create(scratch_pool);
797  for (i = 0; i < merged_chunk->nelts; i++)
798    {
799      apr_size_t bytes_written;
800      svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i,
801                                            svn_stringbuf_t *);
802
803      svn_pool_clear(iterpool);
804
805      SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
806                                     &bytes_written, iterpool));
807      if (line->len != bytes_written)
808        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
809                                _("Could not write data to merged file"));
810    }
811  svn_pool_destroy(iterpool);
812
813  return SVN_NO_ERROR;
814}
815
816/* Original, modified, and latest all differ from one another.
817 * This is a conflict and we'll need to ask the user to merge it. */
818static svn_error_t *
819file_merge_output_conflict(void *output_baton,
820                           apr_off_t original_start,
821                           apr_off_t original_length,
822                           apr_off_t modified_start,
823                           apr_off_t modified_length,
824                           apr_off_t latest_start,
825                           apr_off_t latest_length,
826                           svn_diff_t *resolved_diff)
827{
828  struct file_merge_baton *b = output_baton;
829
830  if (b->abort_merge)
831    return SVN_NO_ERROR;
832
833  SVN_ERR(merge_file_chunks(&b->remains_in_conflict,
834                            &b->abort_merge,
835                            b->merged_file,
836                            b->modified_file,
837                            b->latest_file,
838                            modified_start,
839                            modified_length,
840                            latest_start,
841                            latest_length,
842                            &b->current_line_modified,
843                            &b->current_line_latest,
844                            b->editor_cmd,
845                            b->config,
846                            b->scratch_pool));
847  return SVN_NO_ERROR;
848}
849
850/* Our collection of diff output functions that get driven during the merge. */
851static svn_diff_output_fns_t file_merge_diff_output_fns = {
852  file_merge_output_common,
853  file_merge_output_diff_modified,
854  file_merge_output_diff_latest,
855  file_merge_output_diff_common,
856  file_merge_output_conflict
857};
858
859svn_error_t *
860svn_cl__merge_file(svn_boolean_t *remains_in_conflict,
861                   const char *base_path,
862                   const char *their_path,
863                   const char *my_path,
864                   const char *merged_path,
865                   const char *wc_path,
866                   const char *path_prefix,
867                   const char *editor_cmd,
868                   apr_hash_t *config,
869                   svn_cancel_func_t cancel_func,
870                   void *cancel_baton,
871                   apr_pool_t *scratch_pool)
872{
873  svn_diff_t *diff;
874  svn_diff_file_options_t *diff_options;
875  apr_file_t *original_file;
876  apr_file_t *modified_file;
877  apr_file_t *latest_file;
878  apr_file_t *merged_file;
879  const char *merged_file_name;
880  struct file_merge_baton fmb;
881  svn_boolean_t executable;
882  const char *merged_path_local_style;
883  const char *merged_rel_path;
884  const char *wc_path_local_style;
885  const char *wc_rel_path = svn_dirent_skip_ancestor(path_prefix, wc_path);
886
887  /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the
888     full WC_PATH in that case. */
889  if (wc_rel_path)
890    wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool);
891  else
892    wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool);
893
894  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"),
895                             wc_path_local_style));
896
897  SVN_ERR(svn_io_file_open(&original_file, base_path,
898                           APR_READ | APR_BUFFERED,
899                           APR_OS_DEFAULT, scratch_pool));
900  SVN_ERR(svn_io_file_open(&modified_file, their_path,
901                           APR_READ | APR_BUFFERED,
902                           APR_OS_DEFAULT, scratch_pool));
903  SVN_ERR(svn_io_file_open(&latest_file, my_path,
904                           APR_READ | APR_BUFFERED,
905                           APR_OS_DEFAULT, scratch_pool));
906  SVN_ERR(svn_io_open_unique_file3(&merged_file, &merged_file_name,
907                                   NULL, svn_io_file_del_none,
908                                   scratch_pool, scratch_pool));
909
910  diff_options = svn_diff_file_options_create(scratch_pool);
911  SVN_ERR(svn_diff_file_diff3_2(&diff, base_path, their_path, my_path,
912                                diff_options, scratch_pool));
913
914  fmb.original_file = original_file;
915  fmb.modified_file = modified_file;
916  fmb.latest_file = latest_file;
917  fmb.current_line_original = 0;
918  fmb.current_line_modified = 0;
919  fmb.current_line_latest = 0;
920  fmb.merged_file = merged_file;
921  fmb.remains_in_conflict = FALSE;
922  fmb.editor_cmd = editor_cmd;
923  fmb.config = config;
924  fmb.abort_merge = FALSE;
925  fmb.scratch_pool = scratch_pool;
926
927  SVN_ERR(svn_diff_output2(diff, &fmb, &file_merge_diff_output_fns,
928                           cancel_func, cancel_baton));
929
930  SVN_ERR(svn_io_file_close(original_file, scratch_pool));
931  SVN_ERR(svn_io_file_close(modified_file, scratch_pool));
932  SVN_ERR(svn_io_file_close(latest_file, scratch_pool));
933  SVN_ERR(svn_io_file_close(merged_file, scratch_pool));
934
935  /* Start out assuming that conflicts remain. */
936  if (remains_in_conflict)
937    *remains_in_conflict = TRUE;
938
939  if (fmb.abort_merge)
940    {
941      SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
942      SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merge of '%s' aborted.\n"),
943                                 wc_path_local_style));
944      return SVN_NO_ERROR;
945    }
946
947  SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool));
948
949  merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path);
950  if (merged_rel_path)
951    merged_path_local_style = svn_dirent_local_style(merged_rel_path,
952                                                     scratch_pool);
953  else
954    merged_path_local_style = svn_dirent_local_style(merged_path,
955                                                     scratch_pool);
956
957  SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE,
958                             scratch_pool),
959            apr_psprintf(scratch_pool,
960                         _("Could not write merged result to '%s', saved "
961                           "instead at '%s'.\n'%s' remains in conflict.\n"),
962                         merged_path_local_style,
963                         svn_dirent_local_style(merged_file_name,
964                                                scratch_pool),
965                         wc_path_local_style));
966  SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE,
967                                     scratch_pool));
968  SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
969
970  /* The merge was not aborted and we could install the merged result. The
971   * file remains in conflict unless all conflicting sections were resolved. */
972  if (remains_in_conflict)
973    *remains_in_conflict = fmb.remains_in_conflict;
974
975  if (fmb.remains_in_conflict)
976    SVN_ERR(svn_cmdline_printf(
977              scratch_pool,
978              _("Merge of '%s' completed (remains in conflict).\n"),
979              wc_path_local_style));
980  else
981    SVN_ERR(svn_cmdline_printf(
982              scratch_pool, _("Merge of '%s' completed.\n"),
983              wc_path_local_style));
984
985  return SVN_NO_ERROR;
986}
987