| /* |
| * 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> |
| |
| #if defined(HAVE_TERMIOS_H) |
| #include <termios.h> |
| #endif |
| |
| /* 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; |
| |
| /* Whether the merge should be aborted. */ |
| svn_boolean_t abort_merge; |
| |
| /* Pool for temporary allocations. */ |
| apr_pool_t *scratch_pool; |
| }; |
| |
| /* 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(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(svn_boolean_t *remains_in_conflict, |
| 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_cancel_func_t cancel_func, |
| void *cancel_baton, |
| 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_output2(diff, &fmb, &file_merge_diff_output_fns, |
| cancel_func, cancel_baton)); |
| |
| 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; |
| } |