#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
struct file_merge_baton {
apr_file_t *original_file;
apr_file_t *modified_file;
apr_file_t *latest_file;
svn_linenum_t current_line_original;
svn_linenum_t current_line_modified;
svn_linenum_t current_line_latest;
apr_file_t *merged_file;
svn_boolean_t remains_in_conflict;
const char *editor_cmd;
apr_hash_t *config;
svn_boolean_t abort_merge;
apr_pool_t *scratch_pool;
};
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;
}
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;
}
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;
}
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;
}
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;
}
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 *));
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);
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;
}
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)
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;
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);
width = svn_utf_cstring_utf8_width(buf->data);
if (width == -1)
{
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;
}
iterpool = svn_pool_create(pool);
while (width > line_width)
{
const char *last_valid;
svn_pool_clear(iterpool);
svn_stringbuf_chop(buf, 1);
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;
}
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;
}
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 *));
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;
}
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;
}
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;
}
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;
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: "));
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;
}
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 (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;
}
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;
}
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);
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));
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));
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;
}