summaryrefslogtreecommitdiff
path: root/subversion/svn/file-merge.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svn/file-merge.c')
-rw-r--r--subversion/svn/file-merge.c979
1 files changed, 979 insertions, 0 deletions
diff --git a/subversion/svn/file-merge.c b/subversion/svn/file-merge.c
new file mode 100644
index 0000000..c64f577
--- /dev/null
+++ b/subversion/svn/file-merge.c
@@ -0,0 +1,979 @@
+/*
+ * 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"
+ " (12) their version first, then yours,\n"
+ " (21) your version first, then theirs,\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;
+ }
+ if (strcmp(answer, "12") == 0)
+ {
+ *merged_chunk = apr_array_make(result_pool,
+ chunk1->nelts + chunk2->nelts,
+ sizeof(svn_stringbuf_t *));
+ apr_array_cat(*merged_chunk, chunk1);
+ apr_array_cat(*merged_chunk, chunk2);
+ break;
+ }
+ if (strcmp(answer, "21") == 0)
+ {
+ *merged_chunk = apr_array_make(result_pool,
+ chunk1->nelts + chunk2->nelts,
+ sizeof(svn_stringbuf_t *));
+ apr_array_cat(*merged_chunk, chunk2);
+ apr_array_cat(*merged_chunk, chunk1);
+ 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;
+}