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 /* 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;
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;
66 /* The merge result is written to this file. */
67 apr_file_t *merged_file;
69 /* Whether the merged file remains in conflict after the merge. */
70 svn_boolean_t remains_in_conflict;
72 /* External editor command for editing chunks. */
73 const char *editor_cmd;
75 /* The client configuration hash. */
78 /* Wether the merge should be aborted. */
79 svn_boolean_t abort_merge;
81 /* Pool for temporary allocations. */
82 apr_pool_t *scratch_pool;
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. */
89 copy_to_merged_file(svn_linenum_t *new_current_line,
90 apr_file_t *merged_file,
91 apr_file_t *source_file,
94 svn_linenum_t current_line,
95 apr_pool_t *scratch_pool)
98 svn_stringbuf_t *line;
99 apr_size_t lines_read;
100 apr_size_t lines_copied;
102 svn_linenum_t orig_current_line = current_line;
105 iterpool = svn_pool_create(scratch_pool);
106 while (current_line < start)
108 svn_pool_clear(iterpool);
110 SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof,
111 APR_SIZE_MAX, iterpool, iterpool));
120 while (lines_copied < len)
122 apr_size_t bytes_written;
125 svn_pool_clear(iterpool);
127 SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof,
128 APR_SIZE_MAX, iterpool, iterpool));
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"));
140 svn_pool_destroy(iterpool);
142 *new_current_line = orig_current_line + lines_read + lines_copied;
147 /* Copy common data to the merged file. */
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)
157 struct file_merge_baton *b = output_baton;
162 SVN_ERR(copy_to_merged_file(&b->current_line_original,
167 b->current_line_original,
172 /* Original/latest match up, but modified differs.
173 * Copy modified data to the merged file. */
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)
183 struct file_merge_baton *b = output_baton;
188 SVN_ERR(copy_to_merged_file(&b->current_line_modified,
193 b->current_line_modified,
199 /* Original/modified match up, but latest differs.
200 * Copy latest data to the merged file. */
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)
210 struct file_merge_baton *b = output_baton;
215 SVN_ERR(copy_to_merged_file(&b->current_line_latest,
220 b->current_line_latest,
226 /* Modified/latest match up, but original differs.
227 * Copy latest data to the merged file. */
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)
237 struct file_merge_baton *b = output_baton;
242 SVN_ERR(copy_to_merged_file(&b->current_line_latest,
247 b->current_line_latest,
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. */
257 read_diff_chunk(apr_array_header_t **lines,
258 svn_linenum_t *new_current_line,
260 svn_linenum_t current_line,
263 apr_pool_t *result_pool,
264 apr_pool_t *scratch_pool)
266 svn_stringbuf_t *line;
269 apr_pool_t *iterpool;
271 *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
273 /* Skip lines before start of range. */
274 iterpool = svn_pool_create(scratch_pool);
275 while (current_line < start)
277 svn_pool_clear(iterpool);
278 SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX,
279 iterpool, iterpool));
284 svn_pool_destroy(iterpool);
286 /* Now read the lines. */
289 SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX,
290 result_pool, scratch_pool));
292 svn_stringbuf_appendcstr(line, eol_str);
293 APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line;
298 while ((*lines)->nelts < len);
300 *new_current_line = current_line;
305 /* Return the terminal width in number of columns. */
313 fd = open("/dev/tty", O_RDONLY, 0);
319 error = ioctl(fd, TIOCGWINSZ, &ws);
329 CONSOLE_SCREEN_BUFFER_INFO csbi;
331 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
333 if (csbi.dwSize.X < 80)
335 return csbi.dwSize.X;
339 columns_env = getenv("COLUMNS");
345 err = svn_cstring_atoi(&cols, columns_env);
348 svn_error_clear(err);
360 #define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2)
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. */
366 prepare_line_for_display(const char *line, apr_pool_t *pool)
368 svn_stringbuf_t *buf = svn_stringbuf_create(line, pool);
370 size_t line_width = LINE_DISPLAY_WIDTH;
371 apr_pool_t *iterpool;
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);
383 /* Determine the on-screen width of the line. */
384 width = svn_utf_cstring_utf8_width(buf->data);
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);
392 width = buf->len; /* fallback: buffer length */
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)
400 const char *last_valid;
402 svn_pool_clear(iterpool);
404 svn_stringbuf_chop(buf, 1);
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);
412 width = svn_utf_cstring_utf8_width(buf->data);
414 width = buf->len; /* fallback: buffer length */
416 svn_pool_destroy(iterpool);
418 while (width == 0 || width < line_width)
420 svn_stringbuf_appendbyte(buf, ' ');
424 SVN_ERR_ASSERT_NO_RETURN(width == line_width);
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)
434 apr_array_header_t *merged_chunk;
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++)
443 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
444 APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*);
446 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
447 svn_stringbuf_create("=======\n", result_pool);
448 for (i = 0; i < chunk2->nelts; i++)
450 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
451 APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*);
453 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
454 svn_stringbuf_create(">>>>>>>\n", result_pool);
459 /* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */
461 edit_chunk(apr_array_header_t **merged_chunk,
462 apr_array_header_t *chunk,
463 const char *editor_cmd,
465 apr_pool_t *result_pool,
466 apr_pool_t *scratch_pool)
468 apr_file_t *temp_file;
469 const char *temp_file_name;
474 apr_pool_t *iterpool;
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++)
482 svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *);
483 apr_size_t bytes_written;
485 svn_pool_clear(iterpool);
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"));
493 SVN_ERR(svn_io_file_flush_to_disk(temp_file, scratch_pool));
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))
499 svn_error_t *root_err = svn_error_root_cause(err);
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);
509 else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
511 svn_error_t *root_err = svn_error_root_cause(err);
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);
522 return svn_error_trace(err);
524 *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *));
526 SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool));
529 svn_stringbuf_t *line;
532 svn_pool_clear(iterpool);
534 SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof,
535 APR_SIZE_MAX, result_pool, iterpool));
537 svn_stringbuf_appendcstr(line, eol_str);
539 APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line;
542 svn_pool_destroy(iterpool);
544 SVN_ERR(svn_io_file_close(temp_file, scratch_pool));
549 /* Create a separator string of the appropriate length. */
551 get_sep_string(apr_pool_t *result_pool)
553 int line_width = LINE_DISPLAY_WIDTH;
555 svn_stringbuf_t *buf;
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');
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. */
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,
582 apr_pool_t *result_pool,
583 apr_pool_t *scratch_pool)
585 svn_stringbuf_t *prompt;
588 apr_pool_t *iterpool;
590 max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts
592 *abort_merge = FALSE;
595 * Prepare the selection prompt.
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)"),
606 prepare_line_for_display(
607 apr_psprintf(scratch_pool,
608 _("(2) your version (at line %lu)"),
611 get_sep_string(scratch_pool)),
614 iterpool = svn_pool_create(scratch_pool);
615 for (i = 0; i < max_chunk_lines; i++)
619 const char *prompt_line;
621 svn_pool_clear(iterpool);
623 if (i < chunk1->nelts)
625 svn_stringbuf_t *line_utf8;
627 SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
628 APR_ARRAY_IDX(chunk1, i,
631 line1 = prepare_line_for_display(line_utf8->data, iterpool);
634 line1 = prepare_line_for_display("", iterpool);
636 if (i < chunk2->nelts)
638 svn_stringbuf_t *line_utf8;
640 SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
641 APR_ARRAY_IDX(chunk2, i,
644 line2 = prepare_line_for_display(line_utf8->data, iterpool);
647 line2 = prepare_line_for_display("", iterpool);
649 prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2);
651 svn_stringbuf_appendcstr(prompt, prompt_line);
654 svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool));
655 svn_stringbuf_appendcstr(
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: "));
666 /* Now let's see what the user wants to do with this conflict. */
671 svn_pool_clear(iterpool);
673 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool));
674 if (strcmp(answer, "1") == 0)
676 *merged_chunk = chunk1;
679 else if (strcmp(answer, "2") == 0)
681 *merged_chunk = chunk2;
684 if (strcmp(answer, "12") == 0)
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);
693 if (strcmp(answer, "21") == 0)
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);
702 else if (strcmp(answer, "p") == 0)
704 *merged_chunk = NULL;
707 else if (strcmp(answer, "e1") == 0)
709 SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config,
710 result_pool, iterpool));
714 else if (strcmp(answer, "e2") == 0)
716 SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config,
717 result_pool, iterpool));
721 else if (strcmp(answer, "eb") == 0)
723 apr_array_header_t *conflict_chunk;
725 conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
727 SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config,
728 result_pool, iterpool));
732 else if (strcmp(answer, "a") == 0)
738 svn_pool_destroy(iterpool);
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. */
749 merge_file_chunks(svn_boolean_t *remains_in_conflict,
750 svn_boolean_t *abort_merge,
751 apr_file_t *merged_file,
758 svn_linenum_t *current_line1,
759 svn_linenum_t *current_line2,
760 const char *editor_cmd,
762 apr_pool_t *scratch_pool)
764 apr_array_header_t *chunk1;
765 apr_array_header_t *chunk2;
766 apr_array_header_t *merged_chunk;
767 apr_pool_t *iterpool;
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));
775 SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2,
776 *current_line1, *current_line2,
778 scratch_pool, scratch_pool));
783 /* If the user chose 'postpone' put conflict markers and left/right
784 * versions into the merged file. */
785 if (merged_chunk == NULL)
787 *remains_in_conflict = TRUE;
788 merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
792 iterpool = svn_pool_create(scratch_pool);
793 for (i = 0; i < merged_chunk->nelts; i++)
795 apr_size_t bytes_written;
796 svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i,
799 svn_pool_clear(iterpool);
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"));
807 svn_pool_destroy(iterpool);
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. */
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)
824 struct file_merge_baton *b = output_baton;
829 SVN_ERR(merge_file_chunks(&b->remains_in_conflict,
838 &b->current_line_modified,
839 &b->current_line_latest,
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
856 svn_cl__merge_file(const char *base_path,
857 const char *their_path,
859 const char *merged_path,
861 const char *path_prefix,
862 const char *editor_cmd,
864 svn_boolean_t *remains_in_conflict,
865 apr_pool_t *scratch_pool)
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);
881 /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the
882 full WC_PATH in that case. */
884 wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool);
886 wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool);
888 SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"),
889 wc_path_local_style));
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));
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));
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;
918 fmb.abort_merge = FALSE;
919 fmb.scratch_pool = scratch_pool;
921 SVN_ERR(svn_diff_output(diff, &fmb, &file_merge_diff_output_fns));
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));
928 /* Start out assuming that conflicts remain. */
929 if (remains_in_conflict)
930 *remains_in_conflict = TRUE;
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));
940 SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool));
942 merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path);
944 merged_path_local_style = svn_dirent_local_style(merged_rel_path,
947 merged_path_local_style = svn_dirent_local_style(merged_path,
950 SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE,
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,
958 wc_path_local_style));
959 SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE,
961 SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
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;
968 if (fmb.remains_in_conflict)
969 SVN_ERR(svn_cmdline_printf(
971 _("Merge of '%s' completed (remains in conflict).\n"),
972 wc_path_local_style));
974 SVN_ERR(svn_cmdline_printf(
975 scratch_pool, _("Merge of '%s' completed.\n"),
976 wc_path_local_style));