diff options
Diffstat (limited to 'subversion/svn/file-merge.c')
-rw-r--r-- | subversion/svn/file-merge.c | 959 |
1 files changed, 959 insertions, 0 deletions
diff --git a/subversion/svn/file-merge.c b/subversion/svn/file-merge.c new file mode 100644 index 0000000..43ebba9 --- /dev/null +++ b/subversion/svn/file-merge.c @@ -0,0 +1,959 @@ +/* + * file-merge.c: internal file merge tool + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* This is an interactive file merge tool with an interface similar to + * the interactive mode of the UNIX sdiff ("side-by-side diff") utility. + * The merge tool is driven by Subversion's diff code and user input. */ + +#include "svn_cmdline.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_utf.h" +#include "svn_xml.h" + +#include "cl.h" + +#include "svn_private_config.h" +#include "private/svn_utf_private.h" +#include "private/svn_cmdline_private.h" +#include "private/svn_dep_compat.h" + +#if APR_HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <fcntl.h> +#include <stdlib.h> + +/* Baton for functions in this file which implement svn_diff_output_fns_t. */ +struct file_merge_baton { + /* The files being merged. */ + apr_file_t *original_file; + apr_file_t *modified_file; + apr_file_t *latest_file; + + /* Counters to keep track of the current line in each file. */ + svn_linenum_t current_line_original; + svn_linenum_t current_line_modified; + svn_linenum_t current_line_latest; + + /* The merge result is written to this file. */ + apr_file_t *merged_file; + + /* Whether the merged file remains in conflict after the merge. */ + svn_boolean_t remains_in_conflict; + + /* External editor command for editing chunks. */ + const char *editor_cmd; + + /* The client configuration hash. */ + apr_hash_t *config; + + /* Wether the merge should be aborted. */ + svn_boolean_t abort_merge; + + /* Pool for temporary allocations. */ + apr_pool_t *scratch_pool; +} file_merge_baton; + +/* Copy LEN lines from SOURCE_FILE to the MERGED_FILE, starting at + * line START. The CURRENT_LINE is the current line in the source file. + * The new current line is returned in *NEW_CURRENT_LINE. */ +static svn_error_t * +copy_to_merged_file(svn_linenum_t *new_current_line, + apr_file_t *merged_file, + apr_file_t *source_file, + apr_off_t start, + apr_off_t len, + svn_linenum_t current_line, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + svn_stringbuf_t *line; + apr_size_t lines_read; + apr_size_t lines_copied; + svn_boolean_t eof; + svn_linenum_t orig_current_line = current_line; + + lines_read = 0; + iterpool = svn_pool_create(scratch_pool); + while (current_line < start) + { + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof, + APR_SIZE_MAX, iterpool, iterpool)); + if (eof) + break; + + current_line++; + lines_read++; + } + + lines_copied = 0; + while (lines_copied < len) + { + apr_size_t bytes_written; + const char *eol_str; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof, + APR_SIZE_MAX, iterpool, iterpool)); + if (eol_str) + svn_stringbuf_appendcstr(line, eol_str); + SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len, + &bytes_written, iterpool)); + if (bytes_written != line->len) + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, + _("Could not write data to merged file")); + if (eof) + break; + lines_copied++; + } + svn_pool_destroy(iterpool); + + *new_current_line = orig_current_line + lines_read + lines_copied; + + return SVN_NO_ERROR; +} + +/* Copy common data to the merged file. */ +static svn_error_t * +file_merge_output_common(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(copy_to_merged_file(&b->current_line_original, + b->merged_file, + b->original_file, + original_start, + original_length, + b->current_line_original, + b->scratch_pool)); + return SVN_NO_ERROR; +} + +/* Original/latest match up, but modified differs. + * Copy modified data to the merged file. */ +static svn_error_t * +file_merge_output_diff_modified(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(copy_to_merged_file(&b->current_line_modified, + b->merged_file, + b->modified_file, + modified_start, + modified_length, + b->current_line_modified, + b->scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Original/modified match up, but latest differs. + * Copy latest data to the merged file. */ +static svn_error_t * +file_merge_output_diff_latest(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(copy_to_merged_file(&b->current_line_latest, + b->merged_file, + b->latest_file, + latest_start, + latest_length, + b->current_line_latest, + b->scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Modified/latest match up, but original differs. + * Copy latest data to the merged file. */ +static svn_error_t * +file_merge_output_diff_common(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(copy_to_merged_file(&b->current_line_latest, + b->merged_file, + b->latest_file, + latest_start, + latest_length, + b->current_line_latest, + b->scratch_pool)); + return SVN_NO_ERROR; +} + + +/* Return LEN lines within the diff chunk staring at line START + * in a *LINES array of svn_stringbuf_t* elements. + * Store the resulting current in in *NEW_CURRENT_LINE. */ +static svn_error_t * +read_diff_chunk(apr_array_header_t **lines, + svn_linenum_t *new_current_line, + apr_file_t *file, + svn_linenum_t current_line, + apr_off_t start, + apr_off_t len, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *line; + const char *eol_str; + svn_boolean_t eof; + apr_pool_t *iterpool; + + *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *)); + + /* Skip lines before start of range. */ + iterpool = svn_pool_create(scratch_pool); + while (current_line < start) + { + svn_pool_clear(iterpool); + SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX, + iterpool, iterpool)); + if (eof) + return SVN_NO_ERROR; + current_line++; + } + svn_pool_destroy(iterpool); + + /* Now read the lines. */ + do + { + SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX, + result_pool, scratch_pool)); + if (eol_str) + svn_stringbuf_appendcstr(line, eol_str); + APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line; + if (eof) + break; + current_line++; + } + while ((*lines)->nelts < len); + + *new_current_line = current_line; + + return SVN_NO_ERROR; +} + +/* Return the terminal width in number of columns. */ +static int +get_term_width(void) +{ + char *columns_env; +#ifdef TIOCGWINSZ + int fd; + + fd = open("/dev/tty", O_RDONLY, 0); + if (fd != -1) + { + struct winsize ws; + int error; + + error = ioctl(fd, TIOCGWINSZ, &ws); + close(fd); + if (error != -1) + { + if (ws.ws_col < 80) + return 80; + return ws.ws_col; + } + } +#elif defined WIN32 + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) + { + if (csbi.dwSize.X < 80) + return 80; + return csbi.dwSize.X; + } +#endif + + columns_env = getenv("COLUMNS"); + if (columns_env) + { + svn_error_t *err; + int cols; + + err = svn_cstring_atoi(&cols, columns_env); + if (err) + { + svn_error_clear(err); + return 80; + } + + if (cols < 80) + return 80; + return cols; + } + else + return 80; +} + +#define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2) + +/* Prepare LINE for display, pruning or extending it to an appropriate + * display width, and stripping the EOL marker, if any. + * This function assumes that the data in LINE is encoded in UTF-8. */ +static const char * +prepare_line_for_display(const char *line, apr_pool_t *pool) +{ + svn_stringbuf_t *buf = svn_stringbuf_create(line, pool); + size_t width; + size_t line_width = LINE_DISPLAY_WIDTH; + apr_pool_t *iterpool; + + /* Trim EOL. */ + if (buf->len >= 2 && + buf->data[buf->len - 2] == '\r' && + buf->data[buf->len - 1] == '\n') + svn_stringbuf_chop(buf, 2); + else if (buf->len >= 1 && + (buf->data[buf->len - 1] == '\n' || + buf->data[buf->len - 1] == '\r')) + svn_stringbuf_chop(buf, 1); + + /* Determine the on-screen width of the line. */ + width = svn_utf_cstring_utf8_width(buf->data); + if (width == -1) + { + /* Determining the width failed. Try to get rid of unprintable + * characters in the line buffer. */ + buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool); + width = svn_utf_cstring_utf8_width(buf->data); + if (width == -1) + width = buf->len; /* fallback: buffer length */ + } + + /* Trim further in case line is still too long, or add padding in case + * it is too short. */ + iterpool = svn_pool_create(pool); + while (width > line_width) + { + const char *last_valid; + + svn_pool_clear(iterpool); + + svn_stringbuf_chop(buf, 1); + + /* Be careful not to invalidate the UTF-8 string by trimming + * just part of a character. */ + last_valid = svn_utf__last_valid(buf->data, buf->len); + if (last_valid < buf->data + buf->len) + svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid); + + width = svn_utf_cstring_utf8_width(buf->data); + if (width == -1) + width = buf->len; /* fallback: buffer length */ + } + svn_pool_destroy(iterpool); + + while (width == 0 || width < line_width) + { + svn_stringbuf_appendbyte(buf, ' '); + width++; + } + + SVN_ERR_ASSERT_NO_RETURN(width == line_width); + return buf->data; +} + +/* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */ +static apr_array_header_t * +merge_chunks_with_conflict_markers(apr_array_header_t *chunk1, + apr_array_header_t *chunk2, + apr_pool_t *result_pool) +{ + apr_array_header_t *merged_chunk; + int i; + + merged_chunk = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *)); + /* ### would be nice to show filenames next to conflict markers */ + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + svn_stringbuf_create("<<<<<<<\n", result_pool); + for (i = 0; i < chunk1->nelts; i++) + { + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*); + } + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + svn_stringbuf_create("=======\n", result_pool); + for (i = 0; i < chunk2->nelts; i++) + { + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*); + } + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + svn_stringbuf_create(">>>>>>>\n", result_pool); + + return merged_chunk; +} + +/* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */ +static svn_error_t * +edit_chunk(apr_array_header_t **merged_chunk, + apr_array_header_t *chunk, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_file_t *temp_file; + const char *temp_file_name; + int i; + apr_off_t pos; + svn_boolean_t eof; + svn_error_t *err; + apr_pool_t *iterpool; + + SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < chunk->nelts; i++) + { + svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *); + apr_size_t bytes_written; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len, + &bytes_written, iterpool)); + if (line->len != bytes_written) + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, + _("Could not write data to temporary file")); + } + SVN_ERR(svn_io_file_flush_to_disk(temp_file, scratch_pool)); + + err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd, + config, scratch_pool); + if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) + { + svn_error_t *root_err = svn_error_root_cause(err); + + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", + root_err->message ? root_err->message : + _("No editor found."))); + svn_error_clear(err); + *merged_chunk = NULL; + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + { + svn_error_t *root_err = svn_error_root_cause(err); + + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", + root_err->message ? root_err->message : + _("Error running editor."))); + svn_error_clear(err); + *merged_chunk = NULL; + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *)); + pos = 0; + SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool)); + do + { + svn_stringbuf_t *line; + const char *eol_str; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof, + APR_SIZE_MAX, result_pool, iterpool)); + if (eol_str) + svn_stringbuf_appendcstr(line, eol_str); + + APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line; + } + while (!eof); + svn_pool_destroy(iterpool); + + SVN_ERR(svn_io_file_close(temp_file, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Create a separator string of the appropriate length. */ +static const char * +get_sep_string(apr_pool_t *result_pool) +{ + int line_width = LINE_DISPLAY_WIDTH; + int i; + svn_stringbuf_t *buf; + + buf = svn_stringbuf_create_empty(result_pool); + for (i = 0; i < line_width; i++) + svn_stringbuf_appendbyte(buf, '-'); + svn_stringbuf_appendbyte(buf, '+'); + for (i = 0; i < line_width; i++) + svn_stringbuf_appendbyte(buf, '-'); + svn_stringbuf_appendbyte(buf, '\n'); + + return buf->data; +} + +/* Merge chunks CHUNK1 and CHUNK2. + * Each lines array contains elements of type svn_stringbuf_t*. + * Return the result in *MERGED_CHUNK, or set *MERGED_CHUNK to NULL in + * case the user chooses to postpone resolution of this chunk. + * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */ +static svn_error_t * +merge_chunks(apr_array_header_t **merged_chunk, + svn_boolean_t *abort_merge, + apr_array_header_t *chunk1, + apr_array_header_t *chunk2, + svn_linenum_t current_line1, + svn_linenum_t current_line2, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *prompt; + int i; + int max_chunk_lines; + apr_pool_t *iterpool; + + max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts + : chunk2->nelts; + *abort_merge = FALSE; + + /* + * Prepare the selection prompt. + */ + + prompt = svn_stringbuf_create( + apr_psprintf(scratch_pool, "%s\n%s|%s\n%s", + _("Conflicting section found during merge:"), + prepare_line_for_display( + apr_psprintf(scratch_pool, + _("(1) their version (at line %lu)"), + current_line1), + scratch_pool), + prepare_line_for_display( + apr_psprintf(scratch_pool, + _("(2) your version (at line %lu)"), + current_line2), + scratch_pool), + get_sep_string(scratch_pool)), + scratch_pool); + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < max_chunk_lines; i++) + { + const char *line1; + const char *line2; + const char *prompt_line; + + svn_pool_clear(iterpool); + + if (i < chunk1->nelts) + { + svn_stringbuf_t *line_utf8; + + SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8, + APR_ARRAY_IDX(chunk1, i, + svn_stringbuf_t*), + iterpool)); + line1 = prepare_line_for_display(line_utf8->data, iterpool); + } + else + line1 = prepare_line_for_display("", iterpool); + + if (i < chunk2->nelts) + { + svn_stringbuf_t *line_utf8; + + SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8, + APR_ARRAY_IDX(chunk2, i, + svn_stringbuf_t*), + iterpool)); + line2 = prepare_line_for_display(line_utf8->data, iterpool); + } + else + line2 = prepare_line_for_display("", iterpool); + + prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2); + + svn_stringbuf_appendcstr(prompt, prompt_line); + } + + svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool)); + svn_stringbuf_appendcstr( + prompt, + _("Select: (1) use their version, (2) use your version,\n" + " (e1) edit their version and use the result,\n" + " (e2) edit your version and use the result,\n" + " (eb) edit both versions and use the result,\n" + " (p) postpone this conflicting section leaving conflict markers,\n" + " (a) abort file merge and return to main menu: ")); + + /* Now let's see what the user wants to do with this conflict. */ + while (TRUE) + { + const char *answer; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool)); + if (strcmp(answer, "1") == 0) + { + *merged_chunk = chunk1; + break; + } + else if (strcmp(answer, "2") == 0) + { + *merged_chunk = chunk2; + break; + } + else if (strcmp(answer, "p") == 0) + { + *merged_chunk = NULL; + break; + } + else if (strcmp(answer, "e1") == 0) + { + SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config, + result_pool, iterpool)); + if (*merged_chunk) + break; + } + else if (strcmp(answer, "e2") == 0) + { + SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config, + result_pool, iterpool)); + if (*merged_chunk) + break; + } + else if (strcmp(answer, "eb") == 0) + { + apr_array_header_t *conflict_chunk; + + conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2, + scratch_pool); + SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config, + result_pool, iterpool)); + if (*merged_chunk) + break; + } + else if (strcmp(answer, "a") == 0) + { + *abort_merge = TRUE; + break; + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Perform a merge of chunks from FILE1 and FILE2, specified by START1/LEN1 + * and START2/LEN2, respectively. Append the result to MERGED_FILE. + * The current line numbers for FILE1 and FILE2 are passed in *CURRENT_LINE1 + * and *CURRENT_LINE2, and will be updated to new values upon return. + * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */ +static svn_error_t * +merge_file_chunks(svn_boolean_t *remains_in_conflict, + svn_boolean_t *abort_merge, + apr_file_t *merged_file, + apr_file_t *file1, + apr_file_t *file2, + apr_off_t start1, + apr_off_t len1, + apr_off_t start2, + apr_off_t len2, + svn_linenum_t *current_line1, + svn_linenum_t *current_line2, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *chunk1; + apr_array_header_t *chunk2; + apr_array_header_t *merged_chunk; + apr_pool_t *iterpool; + int i; + + SVN_ERR(read_diff_chunk(&chunk1, current_line1, file1, *current_line1, + start1, len1, scratch_pool, scratch_pool)); + SVN_ERR(read_diff_chunk(&chunk2, current_line2, file2, *current_line2, + start2, len2, scratch_pool, scratch_pool)); + + SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2, + *current_line1, *current_line2, + editor_cmd, config, + scratch_pool, scratch_pool)); + + if (*abort_merge) + return SVN_NO_ERROR; + + /* If the user chose 'postpone' put conflict markers and left/right + * versions into the merged file. */ + if (merged_chunk == NULL) + { + *remains_in_conflict = TRUE; + merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2, + scratch_pool); + } + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < merged_chunk->nelts; i++) + { + apr_size_t bytes_written; + svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i, + svn_stringbuf_t *); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len, + &bytes_written, iterpool)); + if (line->len != bytes_written) + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, + _("Could not write data to merged file")); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Original, modified, and latest all differ from one another. + * This is a conflict and we'll need to ask the user to merge it. */ +static svn_error_t * +file_merge_output_conflict(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length, + svn_diff_t *resolved_diff) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(merge_file_chunks(&b->remains_in_conflict, + &b->abort_merge, + b->merged_file, + b->modified_file, + b->latest_file, + modified_start, + modified_length, + latest_start, + latest_length, + &b->current_line_modified, + &b->current_line_latest, + b->editor_cmd, + b->config, + b->scratch_pool)); + return SVN_NO_ERROR; +} + +/* Our collection of diff output functions that get driven during the merge. */ +static svn_diff_output_fns_t file_merge_diff_output_fns = { + file_merge_output_common, + file_merge_output_diff_modified, + file_merge_output_diff_latest, + file_merge_output_diff_common, + file_merge_output_conflict +}; + +svn_error_t * +svn_cl__merge_file(const char *base_path, + const char *their_path, + const char *my_path, + const char *merged_path, + const char *wc_path, + const char *path_prefix, + const char *editor_cmd, + apr_hash_t *config, + svn_boolean_t *remains_in_conflict, + apr_pool_t *scratch_pool) +{ + svn_diff_t *diff; + svn_diff_file_options_t *diff_options; + apr_file_t *original_file; + apr_file_t *modified_file; + apr_file_t *latest_file; + apr_file_t *merged_file; + const char *merged_file_name; + struct file_merge_baton fmb; + svn_boolean_t executable; + const char *merged_path_local_style; + const char *merged_rel_path; + const char *wc_path_local_style; + const char *wc_rel_path = svn_dirent_skip_ancestor(path_prefix, wc_path); + + /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the + full WC_PATH in that case. */ + if (wc_rel_path) + wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool); + else + wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool); + + SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"), + wc_path_local_style)); + + SVN_ERR(svn_io_file_open(&original_file, base_path, + APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, scratch_pool)); + SVN_ERR(svn_io_file_open(&modified_file, their_path, + APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, scratch_pool)); + SVN_ERR(svn_io_file_open(&latest_file, my_path, + APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, scratch_pool)); + SVN_ERR(svn_io_open_unique_file3(&merged_file, &merged_file_name, + NULL, svn_io_file_del_none, + scratch_pool, scratch_pool)); + + diff_options = svn_diff_file_options_create(scratch_pool); + SVN_ERR(svn_diff_file_diff3_2(&diff, base_path, their_path, my_path, + diff_options, scratch_pool)); + + fmb.original_file = original_file; + fmb.modified_file = modified_file; + fmb.latest_file = latest_file; + fmb.current_line_original = 0; + fmb.current_line_modified = 0; + fmb.current_line_latest = 0; + fmb.merged_file = merged_file; + fmb.remains_in_conflict = FALSE; + fmb.editor_cmd = editor_cmd; + fmb.config = config; + fmb.abort_merge = FALSE; + fmb.scratch_pool = scratch_pool; + + SVN_ERR(svn_diff_output(diff, &fmb, &file_merge_diff_output_fns)); + + SVN_ERR(svn_io_file_close(original_file, scratch_pool)); + SVN_ERR(svn_io_file_close(modified_file, scratch_pool)); + SVN_ERR(svn_io_file_close(latest_file, scratch_pool)); + SVN_ERR(svn_io_file_close(merged_file, scratch_pool)); + + /* Start out assuming that conflicts remain. */ + if (remains_in_conflict) + *remains_in_conflict = TRUE; + + if (fmb.abort_merge) + { + SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool)); + SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merge of '%s' aborted.\n"), + wc_path_local_style)); + return SVN_NO_ERROR; + } + + SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool)); + + merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path); + if (merged_rel_path) + merged_path_local_style = svn_dirent_local_style(merged_rel_path, + scratch_pool); + else + merged_path_local_style = svn_dirent_local_style(merged_path, + scratch_pool); + + SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE, + scratch_pool), + apr_psprintf(scratch_pool, + _("Could not write merged result to '%s', saved " + "instead at '%s'.\n'%s' remains in conflict.\n"), + merged_path_local_style, + svn_dirent_local_style(merged_file_name, + scratch_pool), + wc_path_local_style)); + SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE, + scratch_pool)); + SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool)); + + /* The merge was not aborted and we could install the merged result. The + * file remains in conflict unless all conflicting sections were resolved. */ + if (remains_in_conflict) + *remains_in_conflict = fmb.remains_in_conflict; + + if (fmb.remains_in_conflict) + SVN_ERR(svn_cmdline_printf( + scratch_pool, + _("Merge of '%s' completed (remains in conflict).\n"), + wc_path_local_style)); + else + SVN_ERR(svn_cmdline_printf( + scratch_pool, _("Merge of '%s' completed.\n"), + wc_path_local_style)); + + return SVN_NO_ERROR; +} |