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