]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/svn/file-merge.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / svn / file-merge.c
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. */
55 struct 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. */
88 static svn_error_t *
89 copy_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. */
148 static svn_error_t *
149 file_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. */
174 static svn_error_t *
175 file_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. */
201 static svn_error_t *
202 file_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. */
228 static svn_error_t *
229 file_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. */
256 static svn_error_t *
257 read_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. */
306 static int
307 get_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. */
365 static const char *
366 prepare_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. */
429 static apr_array_header_t *
430 merge_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. */
460 static svn_error_t *
461 edit_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. */
550 static const char *
551 get_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. */
573 static svn_error_t *
574 merge_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. */
748 static svn_error_t *
749 merge_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. */
814 static svn_error_t *
815 file_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. */
847 static 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
855 svn_error_t *
856 svn_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 }