2 * file-merge.c: internal file merge tool
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
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. */
28 #include "svn_cmdline.h"
29 #include "svn_dirent_uri.h"
30 #include "svn_error.h"
31 #include "svn_pools.h"
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"
43 #if APR_HAVE_SYS_IOCTL_H
44 #include <sys/ioctl.h>
54 #if defined(HAVE_TERMIOS_H)
58 /* Baton for functions in this file which implement svn_diff_output_fns_t. */
59 struct 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;
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;
70 /* The merge result is written to this file. */
71 apr_file_t *merged_file;
73 /* Whether the merged file remains in conflict after the merge. */
74 svn_boolean_t remains_in_conflict;
76 /* External editor command for editing chunks. */
77 const char *editor_cmd;
79 /* The client configuration hash. */
82 /* Whether the merge should be aborted. */
83 svn_boolean_t abort_merge;
85 /* Pool for temporary allocations. */
86 apr_pool_t *scratch_pool;
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. */
93 copy_to_merged_file(svn_linenum_t *new_current_line,
94 apr_file_t *merged_file,
95 apr_file_t *source_file,
98 svn_linenum_t current_line,
99 apr_pool_t *scratch_pool)
101 apr_pool_t *iterpool;
102 svn_stringbuf_t *line;
103 apr_size_t lines_read;
104 apr_size_t lines_copied;
106 svn_linenum_t orig_current_line = current_line;
109 iterpool = svn_pool_create(scratch_pool);
110 while (current_line < start)
112 svn_pool_clear(iterpool);
114 SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof,
115 APR_SIZE_MAX, iterpool, iterpool));
124 while (lines_copied < len)
126 apr_size_t bytes_written;
129 svn_pool_clear(iterpool);
131 SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof,
132 APR_SIZE_MAX, iterpool, iterpool));
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"));
144 svn_pool_destroy(iterpool);
146 *new_current_line = orig_current_line + lines_read + lines_copied;
151 /* Copy common data to the merged file. */
153 file_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)
161 struct file_merge_baton *b = output_baton;
166 SVN_ERR(copy_to_merged_file(&b->current_line_original,
171 b->current_line_original,
176 /* Original/latest match up, but modified differs.
177 * Copy modified data to the merged file. */
179 file_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)
187 struct file_merge_baton *b = output_baton;
192 SVN_ERR(copy_to_merged_file(&b->current_line_modified,
197 b->current_line_modified,
203 /* Original/modified match up, but latest differs.
204 * Copy latest data to the merged file. */
206 file_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)
214 struct file_merge_baton *b = output_baton;
219 SVN_ERR(copy_to_merged_file(&b->current_line_latest,
224 b->current_line_latest,
230 /* Modified/latest match up, but original differs.
231 * Copy latest data to the merged file. */
233 file_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)
241 struct file_merge_baton *b = output_baton;
246 SVN_ERR(copy_to_merged_file(&b->current_line_latest,
251 b->current_line_latest,
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. */
261 read_diff_chunk(apr_array_header_t **lines,
262 svn_linenum_t *new_current_line,
264 svn_linenum_t current_line,
267 apr_pool_t *result_pool,
268 apr_pool_t *scratch_pool)
270 svn_stringbuf_t *line;
273 apr_pool_t *iterpool;
275 *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
277 /* Skip lines before start of range. */
278 iterpool = svn_pool_create(scratch_pool);
279 while (current_line < start)
281 svn_pool_clear(iterpool);
282 SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX,
283 iterpool, iterpool));
288 svn_pool_destroy(iterpool);
290 /* Now read the lines. */
293 SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX,
294 result_pool, scratch_pool));
296 svn_stringbuf_appendcstr(line, eol_str);
297 APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line;
302 while ((*lines)->nelts < len);
304 *new_current_line = current_line;
309 /* Return the terminal width in number of columns. */
317 fd = open("/dev/tty", O_RDONLY, 0);
323 error = ioctl(fd, TIOCGWINSZ, &ws);
333 CONSOLE_SCREEN_BUFFER_INFO csbi;
335 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
337 if (csbi.dwSize.X < 80)
339 return csbi.dwSize.X;
343 columns_env = getenv("COLUMNS");
349 err = svn_cstring_atoi(&cols, columns_env);
352 svn_error_clear(err);
364 #define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2)
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. */
370 prepare_line_for_display(const char *line, apr_pool_t *pool)
372 svn_stringbuf_t *buf = svn_stringbuf_create(line, pool);
374 size_t line_width = LINE_DISPLAY_WIDTH;
375 apr_pool_t *iterpool;
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);
387 /* Determine the on-screen width of the line. */
388 width = svn_utf_cstring_utf8_width(buf->data);
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);
396 width = buf->len; /* fallback: buffer length */
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)
404 const char *last_valid;
406 svn_pool_clear(iterpool);
408 svn_stringbuf_chop(buf, 1);
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);
416 width = svn_utf_cstring_utf8_width(buf->data);
418 width = buf->len; /* fallback: buffer length */
420 svn_pool_destroy(iterpool);
422 while (width == 0 || width < line_width)
424 svn_stringbuf_appendbyte(buf, ' ');
428 SVN_ERR_ASSERT_NO_RETURN(width == line_width);
432 /* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */
433 static apr_array_header_t *
434 merge_chunks_with_conflict_markers(apr_array_header_t *chunk1,
435 apr_array_header_t *chunk2,
436 apr_pool_t *result_pool)
438 apr_array_header_t *merged_chunk;
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++)
447 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
448 APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*);
450 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
451 svn_stringbuf_create("=======\n", result_pool);
452 for (i = 0; i < chunk2->nelts; i++)
454 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
455 APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*);
457 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
458 svn_stringbuf_create(">>>>>>>\n", result_pool);
463 /* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */
465 edit_chunk(apr_array_header_t **merged_chunk,
466 apr_array_header_t *chunk,
467 const char *editor_cmd,
469 apr_pool_t *result_pool,
470 apr_pool_t *scratch_pool)
472 apr_file_t *temp_file;
473 const char *temp_file_name;
478 apr_pool_t *iterpool;
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++)
486 svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *);
487 apr_size_t bytes_written;
489 svn_pool_clear(iterpool);
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"));
497 SVN_ERR(svn_io_file_flush(temp_file, scratch_pool));
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))
503 svn_error_t *root_err = svn_error_root_cause(err);
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);
513 else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
515 svn_error_t *root_err = svn_error_root_cause(err);
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);
526 return svn_error_trace(err);
528 *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *));
530 SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool));
533 svn_stringbuf_t *line;
536 svn_pool_clear(iterpool);
538 SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof,
539 APR_SIZE_MAX, result_pool, iterpool));
541 svn_stringbuf_appendcstr(line, eol_str);
543 APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line;
546 svn_pool_destroy(iterpool);
548 SVN_ERR(svn_io_file_close(temp_file, scratch_pool));
553 /* Create a separator string of the appropriate length. */
555 get_sep_string(apr_pool_t *result_pool)
557 int line_width = LINE_DISPLAY_WIDTH;
559 svn_stringbuf_t *buf;
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');
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. */
578 merge_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,
586 apr_pool_t *result_pool,
587 apr_pool_t *scratch_pool)
589 svn_stringbuf_t *prompt;
592 apr_pool_t *iterpool;
594 max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts
596 *abort_merge = FALSE;
599 * Prepare the selection prompt.
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)"),
610 prepare_line_for_display(
611 apr_psprintf(scratch_pool,
612 _("(2) your version (at line %lu)"),
615 get_sep_string(scratch_pool)),
618 iterpool = svn_pool_create(scratch_pool);
619 for (i = 0; i < max_chunk_lines; i++)
623 const char *prompt_line;
625 svn_pool_clear(iterpool);
627 if (i < chunk1->nelts)
629 svn_stringbuf_t *line_utf8;
631 SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
632 APR_ARRAY_IDX(chunk1, i,
635 line1 = prepare_line_for_display(line_utf8->data, iterpool);
638 line1 = prepare_line_for_display("", iterpool);
640 if (i < chunk2->nelts)
642 svn_stringbuf_t *line_utf8;
644 SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
645 APR_ARRAY_IDX(chunk2, i,
648 line2 = prepare_line_for_display(line_utf8->data, iterpool);
651 line2 = prepare_line_for_display("", iterpool);
653 prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2);
655 svn_stringbuf_appendcstr(prompt, prompt_line);
658 svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool));
659 svn_stringbuf_appendcstr(
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: "));
670 /* Now let's see what the user wants to do with this conflict. */
675 svn_pool_clear(iterpool);
677 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool));
678 if (strcmp(answer, "1") == 0)
680 *merged_chunk = chunk1;
683 else if (strcmp(answer, "2") == 0)
685 *merged_chunk = chunk2;
688 if (strcmp(answer, "12") == 0)
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);
697 if (strcmp(answer, "21") == 0)
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);
706 else if (strcmp(answer, "p") == 0)
708 *merged_chunk = NULL;
711 else if (strcmp(answer, "e1") == 0)
713 SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config,
714 result_pool, iterpool));
718 else if (strcmp(answer, "e2") == 0)
720 SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config,
721 result_pool, iterpool));
725 else if (strcmp(answer, "eb") == 0)
727 apr_array_header_t *conflict_chunk;
729 conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
731 SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config,
732 result_pool, iterpool));
736 else if (strcmp(answer, "a") == 0)
742 svn_pool_destroy(iterpool);
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. */
753 merge_file_chunks(svn_boolean_t *remains_in_conflict,
754 svn_boolean_t *abort_merge,
755 apr_file_t *merged_file,
762 svn_linenum_t *current_line1,
763 svn_linenum_t *current_line2,
764 const char *editor_cmd,
766 apr_pool_t *scratch_pool)
768 apr_array_header_t *chunk1;
769 apr_array_header_t *chunk2;
770 apr_array_header_t *merged_chunk;
771 apr_pool_t *iterpool;
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));
779 SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2,
780 *current_line1, *current_line2,
782 scratch_pool, scratch_pool));
787 /* If the user chose 'postpone' put conflict markers and left/right
788 * versions into the merged file. */
789 if (merged_chunk == NULL)
791 *remains_in_conflict = TRUE;
792 merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
796 iterpool = svn_pool_create(scratch_pool);
797 for (i = 0; i < merged_chunk->nelts; i++)
799 apr_size_t bytes_written;
800 svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i,
803 svn_pool_clear(iterpool);
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"));
811 svn_pool_destroy(iterpool);
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. */
819 file_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)
828 struct file_merge_baton *b = output_baton;
833 SVN_ERR(merge_file_chunks(&b->remains_in_conflict,
842 &b->current_line_modified,
843 &b->current_line_latest,
850 /* Our collection of diff output functions that get driven during the merge. */
851 static 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
860 svn_cl__merge_file(svn_boolean_t *remains_in_conflict,
861 const char *base_path,
862 const char *their_path,
864 const char *merged_path,
866 const char *path_prefix,
867 const char *editor_cmd,
869 svn_cancel_func_t cancel_func,
871 apr_pool_t *scratch_pool)
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);
887 /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the
888 full WC_PATH in that case. */
890 wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool);
892 wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool);
894 SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"),
895 wc_path_local_style));
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));
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));
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;
924 fmb.abort_merge = FALSE;
925 fmb.scratch_pool = scratch_pool;
927 SVN_ERR(svn_diff_output2(diff, &fmb, &file_merge_diff_output_fns,
928 cancel_func, cancel_baton));
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));
935 /* Start out assuming that conflicts remain. */
936 if (remains_in_conflict)
937 *remains_in_conflict = TRUE;
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));
947 SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool));
949 merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path);
951 merged_path_local_style = svn_dirent_local_style(merged_rel_path,
954 merged_path_local_style = svn_dirent_local_style(merged_path,
957 SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE,
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,
965 wc_path_local_style));
966 SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE,
968 SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
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;
975 if (fmb.remains_in_conflict)
976 SVN_ERR(svn_cmdline_printf(
978 _("Merge of '%s' completed (remains in conflict).\n"),
979 wc_path_local_style));
981 SVN_ERR(svn_cmdline_printf(
982 scratch_pool, _("Merge of '%s' completed.\n"),
983 wc_path_local_style));