#include <apr.h>
#include <apr_pools.h>
#include <apr_general.h>
#include <apr_file_io.h>
#include <apr_file_info.h>
#include <apr_time.h>
#include <apr_mmap.h>
#include <apr_getopt.h>
#include "svn_error.h"
#include "svn_diff.h"
#include "svn_types.h"
#include "svn_string.h"
#include "svn_subst.h"
#include "svn_io.h"
#include "svn_utf.h"
#include "svn_pools.h"
#include "diff.h"
#include "svn_private_config.h"
#include "svn_path.h"
#include "svn_ctype.h"
#include "private/svn_utf_private.h"
#include "private/svn_eol_private.h"
#include "private/svn_dep_compat.h"
#include "private/svn_adler32.h"
typedef struct svn_diff__file_token_t
{
struct svn_diff__file_token_t *next;
svn_diff_datasource_e datasource;
apr_off_t offset;
apr_off_t norm_offset;
apr_off_t raw_length;
apr_off_t length;
} svn_diff__file_token_t;
typedef struct svn_diff__file_baton_t
{
const svn_diff_file_options_t *options;
struct file_info {
const char *path;
apr_file_t *file;
apr_off_t size;
int chunk;
char *buffer;
char *curp;
char *endp;
svn_diff__normalize_state_t normalize_state;
int suffix_start_chunk;
apr_off_t suffix_offset_in_chunk;
} files[4];
svn_diff__file_token_t *tokens;
apr_pool_t *pool;
} svn_diff__file_baton_t;
static int
datasource_to_index(svn_diff_datasource_e datasource)
{
switch (datasource)
{
case svn_diff_datasource_original:
return 0;
case svn_diff_datasource_modified:
return 1;
case svn_diff_datasource_latest:
return 2;
case svn_diff_datasource_ancestor:
return 3;
}
return -1;
}
#define CHUNK_SHIFT 17
#define CHUNK_SIZE (1 << CHUNK_SHIFT)
#define chunk_to_offset(chunk) ((chunk) << CHUNK_SHIFT)
#define offset_to_chunk(offset) ((offset) >> CHUNK_SHIFT)
#define offset_in_chunk(offset) ((offset) & (CHUNK_SIZE - 1))
static APR_INLINE svn_error_t *
read_chunk(apr_file_t *file, const char *path,
char *buffer, apr_off_t length,
apr_off_t offset, apr_pool_t *pool)
{
SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
return svn_io_file_read_full2(file, buffer, (apr_size_t) length,
NULL, NULL, pool);
}
#if APR_HAS_MMAP
#define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,
#define MMAP_T_ARG(NAME) &(NAME),
#else
#define MMAP_T_PARAM(NAME)
#define MMAP_T_ARG(NAME)
#endif
static svn_error_t *
map_or_read_file(apr_file_t **file,
MMAP_T_PARAM(mm)
char **buffer, apr_off_t *size,
const char *path, apr_pool_t *pool)
{
apr_finfo_t finfo;
apr_status_t rv;
*buffer = NULL;
SVN_ERR(svn_io_file_open(file, path, APR_READ, APR_OS_DEFAULT, pool));
SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, *file, pool));
#if APR_HAS_MMAP
if (finfo.size > APR_MMAP_THRESHOLD)
{
rv = apr_mmap_create(mm, *file, 0, (apr_size_t) finfo.size,
APR_MMAP_READ, pool);
if (rv == APR_SUCCESS)
{
*buffer = (*mm)->mm;
}
}
#endif
if (*buffer == NULL && finfo.size > 0)
{
*buffer = apr_palloc(pool, (apr_size_t) finfo.size);
SVN_ERR(svn_io_file_read_full2(*file, *buffer, (apr_size_t) finfo.size,
NULL, NULL, pool));
SVN_ERR(svn_io_file_close(*file, pool));
*file = NULL;
}
*size = finfo.size;
return SVN_NO_ERROR;
}
#define INCREMENT_POINTERS(all_files, files_len, pool) \
do { \
apr_size_t svn_macro__i; \
\
for (svn_macro__i = 0; svn_macro__i < (files_len); svn_macro__i++) \
{ \
if ((all_files)[svn_macro__i].curp < (all_files)[svn_macro__i].endp - 1)\
(all_files)[svn_macro__i].curp++; \
else \
SVN_ERR(increment_chunk(&(all_files)[svn_macro__i], (pool))); \
} \
} while (0)
#define DECREMENT_POINTERS(all_files, files_len, pool) \
do { \
apr_size_t svn_macro__i; \
\
for (svn_macro__i = 0; svn_macro__i < (files_len); svn_macro__i++) \
{ \
if ((all_files)[svn_macro__i].curp > (all_files)[svn_macro__i].buffer) \
(all_files)[svn_macro__i].curp--; \
else \
SVN_ERR(decrement_chunk(&(all_files)[svn_macro__i], (pool))); \
} \
} while (0)
static svn_error_t *
increment_chunk(struct file_info *file, apr_pool_t *pool)
{
apr_off_t length;
apr_off_t last_chunk = offset_to_chunk(file->size);
if (file->chunk == -1)
{
file->chunk = 0;
file->curp = file->buffer;
}
else if (file->chunk == last_chunk)
{
file->curp = file->endp;
}
else
{
file->chunk++;
length = file->chunk == last_chunk ?
offset_in_chunk(file->size) : CHUNK_SIZE;
SVN_ERR(read_chunk(file->file, file->path, file->buffer,
length, chunk_to_offset(file->chunk),
pool));
file->endp = file->buffer + length;
file->curp = file->buffer;
}
return SVN_NO_ERROR;
}
static svn_error_t *
decrement_chunk(struct file_info *file, apr_pool_t *pool)
{
if (file->chunk == 0)
{
file->chunk--;
file->curp = file->endp - 1;
}
else
{
file->chunk--;
SVN_ERR(read_chunk(file->file, file->path, file->buffer,
CHUNK_SIZE, chunk_to_offset(file->chunk),
pool));
file->endp = file->buffer + CHUNK_SIZE;
file->curp = file->endp - 1;
}
return SVN_NO_ERROR;
}
static svn_boolean_t
is_one_at_bof(struct file_info file[], apr_size_t file_len)
{
apr_size_t i;
for (i = 0; i < file_len; i++)
if (file[i].chunk == -1)
return TRUE;
return FALSE;
}
static svn_boolean_t
is_one_at_eof(struct file_info file[], apr_size_t file_len)
{
apr_size_t i;
for (i = 0; i < file_len; i++)
if (file[i].curp == file[i].endp)
return TRUE;
return FALSE;
}
#if SVN_UNALIGNED_ACCESS_IS_OK
#if APR_SIZEOF_VOIDP == 8
# define LOWER_7BITS_SET 0x7f7f7f7f7f7f7f7f
# define BIT_7_SET 0x8080808080808080
# define R_MASK 0x0a0a0a0a0a0a0a0a
# define N_MASK 0x0d0d0d0d0d0d0d0d
#else
# define LOWER_7BITS_SET 0x7f7f7f7f
# define BIT_7_SET 0x80808080
# define R_MASK 0x0a0a0a0a
# define N_MASK 0x0d0d0d0d
#endif
#endif
#if SVN_UNALIGNED_ACCESS_IS_OK
static svn_boolean_t contains_eol(apr_uintptr_t chunk)
{
apr_uintptr_t r_test = chunk ^ R_MASK;
apr_uintptr_t n_test = chunk ^ N_MASK;
r_test |= (r_test & LOWER_7BITS_SET) + LOWER_7BITS_SET;
n_test |= (n_test & LOWER_7BITS_SET) + LOWER_7BITS_SET;
return (r_test & n_test & BIT_7_SET) != BIT_7_SET;
}
#endif
static svn_error_t *
find_identical_prefix(svn_boolean_t *reached_one_eof, apr_off_t *prefix_lines,
struct file_info file[], apr_size_t file_len,
apr_pool_t *pool)
{
svn_boolean_t had_cr = FALSE;
svn_boolean_t is_match;
apr_off_t lines = 0;
apr_size_t i;
*reached_one_eof = FALSE;
for (i = 1, is_match = TRUE; i < file_len; i++)
is_match = is_match && *file[0].curp == *file[i].curp;
while (is_match)
{
#if SVN_UNALIGNED_ACCESS_IS_OK
apr_ssize_t max_delta, delta;
#endif
if (*file[0].curp == '\r')
{
lines++;
had_cr = TRUE;
}
else if (*file[0].curp == '\n' && !had_cr)
{
lines++;
}
else
{
had_cr = FALSE;
}
INCREMENT_POINTERS(file, file_len, pool);
#if SVN_UNALIGNED_ACCESS_IS_OK
max_delta = file[0].endp - file[0].curp - sizeof(apr_uintptr_t);
for (i = 1; i < file_len; i++)
{
delta = file[i].endp - file[i].curp - sizeof(apr_uintptr_t);
if (delta < max_delta)
max_delta = delta;
}
is_match = TRUE;
for (delta = 0; delta < max_delta; delta += sizeof(apr_uintptr_t))
{
apr_uintptr_t chunk = *(const apr_size_t *)(file[0].curp + delta);
if (contains_eol(chunk))
break;
for (i = 1; i < file_len; i++)
if (chunk != *(const apr_size_t *)(file[i].curp + delta))
{
is_match = FALSE;
break;
}
if (! is_match)
break;
}
if (delta )
{
for (i = 0; i < file_len; i++)
file[i].curp += delta;
had_cr = FALSE;
}
#endif
*reached_one_eof = is_one_at_eof(file, file_len);
if (*reached_one_eof)
break;
else
for (i = 1, is_match = TRUE; i < file_len; i++)
is_match = is_match && *file[0].curp == *file[i].curp;
}
if (had_cr)
{
svn_boolean_t ended_at_nonmatching_newline = FALSE;
for (i = 0; i < file_len; i++)
if (file[i].curp < file[i].endp)
ended_at_nonmatching_newline = ended_at_nonmatching_newline
|| *file[i].curp == '\n';
if (ended_at_nonmatching_newline)
{
lines--;
DECREMENT_POINTERS(file, file_len, pool);
}
}
DECREMENT_POINTERS(file, file_len, pool);
while (!is_one_at_bof(file, file_len) &&
*file[0].curp != '\n' && *file[0].curp != '\r')
DECREMENT_POINTERS(file, file_len, pool);
INCREMENT_POINTERS(file, file_len, pool);
*prefix_lines = lines;
return SVN_NO_ERROR;
}
#ifndef SUFFIX_LINES_TO_KEEP
#define SUFFIX_LINES_TO_KEEP 50
#endif
static svn_error_t *
find_identical_suffix(apr_off_t *suffix_lines, struct file_info file[],
apr_size_t file_len, apr_pool_t *pool)
{
struct file_info file_for_suffix[4] = { { 0 } };
apr_off_t length[4];
apr_off_t suffix_min_chunk0;
apr_off_t suffix_min_offset0;
apr_off_t min_file_size;
int suffix_lines_to_keep = SUFFIX_LINES_TO_KEEP;
svn_boolean_t is_match;
svn_boolean_t reached_prefix;
apr_off_t lines = 0;
svn_boolean_t had_cr;
svn_boolean_t had_nl;
apr_size_t i;
for (i = 0; i < file_len; i++)
{
file_for_suffix[i].path = file[i].path;
file_for_suffix[i].file = file[i].file;
file_for_suffix[i].size = file[i].size;
file_for_suffix[i].chunk =
(int) offset_to_chunk(file_for_suffix[i].size);
length[i] = offset_in_chunk(file_for_suffix[i].size);
if (length[i] == 0)
{
file_for_suffix[i].chunk = file_for_suffix[i].chunk - 1;
length[i] = CHUNK_SIZE;
}
if (file_for_suffix[i].chunk == file[i].chunk)
{
file_for_suffix[i].buffer = file[i].buffer;
}
else
{
file_for_suffix[i].buffer = apr_palloc(pool, CHUNK_SIZE);
SVN_ERR(read_chunk(file_for_suffix[i].file, file_for_suffix[i].path,
file_for_suffix[i].buffer, length[i],
chunk_to_offset(file_for_suffix[i].chunk),
pool));
}
file_for_suffix[i].endp = file_for_suffix[i].buffer + length[i];
file_for_suffix[i].curp = file_for_suffix[i].endp - 1;
}
suffix_min_chunk0 = file[0].chunk;
suffix_min_offset0 = file[0].curp - file[0].buffer;
for (i = 1, min_file_size = file[0].size; i < file_len; i++)
if (file[i].size < min_file_size)
min_file_size = file[i].size;
if (file[0].size > min_file_size)
{
suffix_min_chunk0 += (file[0].size - min_file_size) / CHUNK_SIZE;
suffix_min_offset0 += (file[0].size - min_file_size) % CHUNK_SIZE;
}
for (i = 1, is_match = TRUE; i < file_len; i++)
is_match = is_match
&& *file_for_suffix[0].curp == *file_for_suffix[i].curp;
if (is_match && *file_for_suffix[0].curp != '\r'
&& *file_for_suffix[0].curp != '\n')
lines++;
had_nl = FALSE;
while (is_match)
{
const char *min_curp[4];
#if SVN_UNALIGNED_ACCESS_IS_OK
svn_boolean_t can_read_word;
#endif
if (*file_for_suffix[0].curp == '\n')
{
lines++;
had_nl = TRUE;
}
else if (*file_for_suffix[0].curp == '\r' && !had_nl)
{
lines++;
}
else
{
had_nl = FALSE;
}
DECREMENT_POINTERS(file_for_suffix, file_len, pool);
min_curp[0] = file_for_suffix[0].chunk == suffix_min_chunk0
? file_for_suffix[0].buffer + suffix_min_offset0 + 1
: file_for_suffix[0].buffer + 1;
for (i = 1; i < file_len; i++)
min_curp[i] = file_for_suffix[i].buffer + 1;
#if SVN_UNALIGNED_ACCESS_IS_OK
for (i = 0, can_read_word = TRUE; i < file_len; i++)
can_read_word = can_read_word
&& ( file_for_suffix[i].curp - sizeof(apr_uintptr_t)
>= min_curp[i]);
while (can_read_word)
{
apr_uintptr_t chunk;
chunk = *(const apr_uintptr_t *)(file_for_suffix[0].curp + 1
- sizeof(apr_uintptr_t));
if (contains_eol(chunk))
break;
for (i = 1, is_match = TRUE; i < file_len; i++)
is_match = is_match
&& ( chunk
== *(const apr_uintptr_t *)
(file_for_suffix[i].curp + 1
- sizeof(apr_uintptr_t)));
if (! is_match)
break;
for (i = 0; i < file_len; i++)
{
file_for_suffix[i].curp -= sizeof(apr_uintptr_t);
can_read_word = can_read_word
&& ( (file_for_suffix[i].curp
- sizeof(apr_uintptr_t))
>= min_curp[i]);
}
had_nl = FALSE;
had_cr = FALSE;
}
#endif
reached_prefix = file_for_suffix[0].chunk == suffix_min_chunk0
&& (file_for_suffix[0].curp - file_for_suffix[0].buffer)
== suffix_min_offset0;
if (reached_prefix || is_one_at_bof(file_for_suffix, file_len))
break;
for (i = 1, is_match = TRUE; i < file_len; i++)
is_match = is_match
&& *file_for_suffix[0].curp == *file_for_suffix[i].curp;
}
INCREMENT_POINTERS(file_for_suffix, file_len, pool);
do
{
had_cr = FALSE;
while (!is_one_at_eof(file_for_suffix, file_len)
&& *file_for_suffix[0].curp != '\n'
&& *file_for_suffix[0].curp != '\r')
INCREMENT_POINTERS(file_for_suffix, file_len, pool);
if (!is_one_at_eof(file_for_suffix, file_len)
&& *file_for_suffix[0].curp == '\r')
{
lines--;
had_cr = TRUE;
INCREMENT_POINTERS(file_for_suffix, file_len, pool);
}
if (!is_one_at_eof(file_for_suffix, file_len)
&& *file_for_suffix[0].curp == '\n')
{
if (!had_cr)
lines--;
INCREMENT_POINTERS(file_for_suffix, file_len, pool);
}
}
while (!is_one_at_eof(file_for_suffix, file_len)
&& suffix_lines_to_keep--);
if (is_one_at_eof(file_for_suffix, file_len))
lines = 0;
for (i = 0; i < file_len; i++)
{
file[i].suffix_start_chunk = file_for_suffix[i].chunk;
file[i].suffix_offset_in_chunk =
file_for_suffix[i].curp - file_for_suffix[i].buffer;
}
*suffix_lines = lines;
return SVN_NO_ERROR;
}
static svn_error_t *
datasources_open(void *baton,
apr_off_t *prefix_lines,
apr_off_t *suffix_lines,
const svn_diff_datasource_e *datasources,
apr_size_t datasources_len)
{
svn_diff__file_baton_t *file_baton = baton;
struct file_info files[4];
apr_finfo_t finfo[4];
apr_off_t length[4];
svn_boolean_t reached_one_eof;
apr_size_t i;
*prefix_lines = 0;
*suffix_lines = 0;
for (i = 0; i < datasources_len; i++)
{
struct file_info *file
= &file_baton->files[datasource_to_index(datasources[i])];
SVN_ERR(svn_io_file_open(&file->file, file->path,
APR_READ, APR_OS_DEFAULT, file_baton->pool));
SVN_ERR(svn_io_file_info_get(&finfo[i], APR_FINFO_SIZE,
file->file, file_baton->pool));
file->size = finfo[i].size;
length[i] = finfo[i].size > CHUNK_SIZE ? CHUNK_SIZE : finfo[i].size;
file->buffer = apr_palloc(file_baton->pool, (apr_size_t) length[i]);
SVN_ERR(read_chunk(file->file, file->path, file->buffer,
length[i], 0, file_baton->pool));
file->endp = file->buffer + length[i];
file->curp = file->buffer;
file->suffix_start_chunk = -1;
files[i] = *file;
}
for (i = 0; i < datasources_len; i++)
if (length[i] == 0)
return SVN_NO_ERROR;
#ifndef SVN_DISABLE_PREFIX_SUFFIX_SCANNING
SVN_ERR(find_identical_prefix(&reached_one_eof, prefix_lines,
files, datasources_len, file_baton->pool));
if (!reached_one_eof)
SVN_ERR(find_identical_suffix(suffix_lines, files, datasources_len,
file_baton->pool));
#endif
for (i = 0; i < datasources_len; i++)
file_baton->files[datasource_to_index(datasources[i])] = files[i];
return SVN_NO_ERROR;
}
static svn_error_t *
datasource_close(void *baton, svn_diff_datasource_e datasource)
{
return SVN_NO_ERROR;
}
static svn_error_t *
datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,
svn_diff_datasource_e datasource)
{
svn_diff__file_baton_t *file_baton = baton;
svn_diff__file_token_t *file_token;
struct file_info *file = &file_baton->files[datasource_to_index(datasource)];
char *endp;
char *curp;
char *eol;
apr_off_t last_chunk;
apr_off_t length;
apr_uint32_t h = 0;
svn_boolean_t had_cr = FALSE;
*token = NULL;
curp = file->curp;
endp = file->endp;
last_chunk = offset_to_chunk(file->size);
if (curp == endp)
{
if (last_chunk == file->chunk)
return SVN_NO_ERROR;
if (file->chunk + 1 == file->suffix_start_chunk
&& file->suffix_offset_in_chunk == 0)
return SVN_NO_ERROR;
}
if (file->chunk == file->suffix_start_chunk
&& (curp - file->buffer) == file->suffix_offset_in_chunk)
return SVN_NO_ERROR;
file_token = file_baton->tokens;
if (file_token)
{
file_baton->tokens = file_token->next;
}
else
{
file_token = apr_palloc(file_baton->pool, sizeof(*file_token));
}
file_token->datasource = datasource;
file_token->offset = chunk_to_offset(file->chunk)
+ (curp - file->buffer);
file_token->norm_offset = file_token->offset;
file_token->raw_length = 0;
file_token->length = 0;
while (1)
{
eol = svn_eol__find_eol_start(curp, endp - curp);
if (eol)
{
had_cr = (*eol == '\r');
eol++;
if (!(had_cr && eol == endp))
{
if (had_cr && *eol == '\n')
eol++;
break;
}
}
if (file->chunk == last_chunk)
{
eol = endp;
break;
}
length = endp - curp;
file_token->raw_length += length;
{
char *c = curp;
svn_diff__normalize_buffer(&c, &length,
&file->normalize_state,
curp, file_baton->options);
if (file_token->length == 0)
{
file_token->norm_offset += (c - curp);
}
file_token->length += length;
h = svn__adler32(h, c, length);
}
curp = endp = file->buffer;
file->chunk++;
length = file->chunk == last_chunk ?
offset_in_chunk(file->size) : CHUNK_SIZE;
endp += length;
file->endp = endp;
SVN_ERR(read_chunk(file->file, file->path,
curp, length,
chunk_to_offset(file->chunk),
file_baton->pool));
if (had_cr)
{
eol = curp;
if (*curp == '\n')
++eol;
break;
}
}
length = eol - curp;
file_token->raw_length += length;
file->curp = eol;
if (file_token->raw_length > 0)
{
char *c = curp;
svn_diff__normalize_buffer(&c, &length,
&file->normalize_state,
curp, file_baton->options);
if (file_token->length == 0)
{
file_token->norm_offset += (c - curp);
}
file_token->length += length;
*hash = svn__adler32(h, c, length);
*token = file_token;
}
return SVN_NO_ERROR;
}
#define COMPARE_CHUNK_SIZE 4096
static svn_error_t *
token_compare(void *baton, void *token1, void *token2, int *compare)
{
svn_diff__file_baton_t *file_baton = baton;
svn_diff__file_token_t *file_token[2];
char buffer[2][COMPARE_CHUNK_SIZE];
char *bufp[2];
apr_off_t offset[2];
struct file_info *file[2];
apr_off_t length[2];
apr_off_t total_length;
apr_off_t raw_length[2];
int i;
svn_diff__normalize_state_t state[2];
file_token[0] = token1;
file_token[1] = token2;
if (file_token[0]->length < file_token[1]->length)
{
*compare = -1;
return SVN_NO_ERROR;
}
if (file_token[0]->length > file_token[1]->length)
{
*compare = 1;
return SVN_NO_ERROR;
}
total_length = file_token[0]->length;
if (total_length == 0)
{
*compare = 0;
return SVN_NO_ERROR;
}
for (i = 0; i < 2; ++i)
{
int idx = datasource_to_index(file_token[i]->datasource);
file[i] = &file_baton->files[idx];
offset[i] = file_token[i]->norm_offset;
state[i] = svn_diff__normalize_state_normal;
if (offset_to_chunk(offset[i]) == file[i]->chunk)
{
bufp[i] = file[i]->buffer;
bufp[i] += offset_in_chunk(offset[i]);
length[i] = total_length;
raw_length[i] = 0;
}
else
{
apr_off_t skipped;
length[i] = 0;
skipped = (file_token[i]->norm_offset - file_token[i]->offset);
raw_length[i] = file_token[i]->raw_length - skipped;
}
}
do
{
apr_off_t len;
for (i = 0; i < 2; i++)
{
if (length[i] == 0)
{
if (raw_length[i] == 0)
return svn_error_createf(SVN_ERR_DIFF_DATASOURCE_MODIFIED,
NULL,
_("The file '%s' changed unexpectedly"
" during diff"),
file[i]->path);
bufp[i] = buffer[i];
length[i] = raw_length[i] > COMPARE_CHUNK_SIZE ?
COMPARE_CHUNK_SIZE : raw_length[i];
SVN_ERR(read_chunk(file[i]->file,
file[i]->path,
bufp[i], length[i], offset[i],
file_baton->pool));
offset[i] += length[i];
raw_length[i] -= length[i];
svn_diff__normalize_buffer(&bufp[i], &length[i], &state[i],
bufp[i], file_baton->options);
}
}
len = length[0] > length[1] ? length[1] : length[0];
*compare = memcmp(bufp[0], bufp[1], (size_t) len);
if (*compare != 0)
return SVN_NO_ERROR;
total_length -= len;
length[0] -= len;
length[1] -= len;
bufp[0] += len;
bufp[1] += len;
}
while(total_length > 0);
*compare = 0;
return SVN_NO_ERROR;
}
static void
token_discard(void *baton, void *token)
{
svn_diff__file_baton_t *file_baton = baton;
svn_diff__file_token_t *file_token = token;
file_token->next = file_baton->tokens;
file_baton->tokens = file_token;
}
static void
token_discard_all(void *baton)
{
svn_diff__file_baton_t *file_baton = baton;
svn_pool_clear(file_baton->pool);
}
static const svn_diff_fns2_t svn_diff__file_vtable =
{
datasources_open,
datasource_close,
datasource_get_next_token,
token_compare,
token_discard,
token_discard_all
};
#define SVN_DIFF__OPT_IGNORE_EOL_STYLE 256
static const apr_getopt_option_t diff_options[] =
{
{ "ignore-space-change", 'b', 0, NULL },
{ "ignore-all-space", 'w', 0, NULL },
{ "ignore-eol-style", SVN_DIFF__OPT_IGNORE_EOL_STYLE, 0, NULL },
{ "show-c-function", 'p', 0, NULL },
{ "unified", 'u', 0, NULL },
{ NULL, 0, 0, NULL }
};
svn_diff_file_options_t *
svn_diff_file_options_create(apr_pool_t *pool)
{
return apr_pcalloc(pool, sizeof(svn_diff_file_options_t));
}
struct opt_parsing_error_baton_t
{
svn_error_t *err;
apr_pool_t *pool;
};
static void
opt_parsing_error_func(void *baton,
const char *fmt, ...)
{
struct opt_parsing_error_baton_t *b = baton;
const char *message;
va_list ap;
va_start(ap, fmt);
message = apr_pvsprintf(b->pool, fmt, ap);
va_end(ap);
if (strncmp(message, ": ", 2) == 0)
message += 2;
b->err = svn_error_create(SVN_ERR_INVALID_DIFF_OPTION, NULL, message);
}
svn_error_t *
svn_diff_file_options_parse(svn_diff_file_options_t *options,
const apr_array_header_t *args,
apr_pool_t *pool)
{
apr_getopt_t *os;
struct opt_parsing_error_baton_t opt_parsing_error_baton;
const char **argv = apr_palloc(pool, sizeof(char*) * (args->nelts + 2));
opt_parsing_error_baton.err = NULL;
opt_parsing_error_baton.pool = pool;
argv[0] = "";
memcpy((void *) (argv + 1), args->elts, sizeof(char*) * args->nelts);
argv[args->nelts + 1] = NULL;
apr_getopt_init(&os, pool, args->nelts + 1, argv);
os->errfn = opt_parsing_error_func;
os->errarg = &opt_parsing_error_baton;
while (1)
{
const char *opt_arg;
int opt_id;
apr_status_t err = apr_getopt_long(os, diff_options, &opt_id, &opt_arg);
if (APR_STATUS_IS_EOF(err))
break;
if (err)
return svn_error_create(SVN_ERR_INVALID_DIFF_OPTION,
opt_parsing_error_baton.err,
_("Error in options to internal diff"));
switch (opt_id)
{
case 'b':
if (! options->ignore_space)
options->ignore_space = svn_diff_file_ignore_space_change;
break;
case 'w':
options->ignore_space = svn_diff_file_ignore_space_all;
break;
case SVN_DIFF__OPT_IGNORE_EOL_STYLE:
options->ignore_eol_style = TRUE;
break;
case 'p':
options->show_c_function = TRUE;
break;
default:
break;
}
}
if (os->ind < os->argc)
return svn_error_createf(SVN_ERR_INVALID_DIFF_OPTION, NULL,
_("Invalid argument '%s' in diff options"),
os->argv[os->ind]);
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff_file_diff_2(svn_diff_t **diff,
const char *original,
const char *modified,
const svn_diff_file_options_t *options,
apr_pool_t *pool)
{
svn_diff__file_baton_t baton = { 0 };
baton.options = options;
baton.files[0].path = original;
baton.files[1].path = modified;
baton.pool = svn_pool_create(pool);
SVN_ERR(svn_diff_diff_2(diff, &baton, &svn_diff__file_vtable, pool));
svn_pool_destroy(baton.pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff_file_diff3_2(svn_diff_t **diff,
const char *original,
const char *modified,
const char *latest,
const svn_diff_file_options_t *options,
apr_pool_t *pool)
{
svn_diff__file_baton_t baton = { 0 };
baton.options = options;
baton.files[0].path = original;
baton.files[1].path = modified;
baton.files[2].path = latest;
baton.pool = svn_pool_create(pool);
SVN_ERR(svn_diff_diff3_2(diff, &baton, &svn_diff__file_vtable, pool));
svn_pool_destroy(baton.pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff_file_diff4_2(svn_diff_t **diff,
const char *original,
const char *modified,
const char *latest,
const char *ancestor,
const svn_diff_file_options_t *options,
apr_pool_t *pool)
{
svn_diff__file_baton_t baton = { 0 };
baton.options = options;
baton.files[0].path = original;
baton.files[1].path = modified;
baton.files[2].path = latest;
baton.files[3].path = ancestor;
baton.pool = svn_pool_create(pool);
SVN_ERR(svn_diff_diff4_2(diff, &baton, &svn_diff__file_vtable, pool));
svn_pool_destroy(baton.pool);
return SVN_NO_ERROR;
}
#define SVN_DIFF__EXTRA_CONTEXT_LENGTH 50
typedef struct svn_diff__file_output_baton_t
{
svn_stream_t *output_stream;
const char *header_encoding;
const char *context_str;
const char *delete_str;
const char *insert_str;
const char *path[2];
apr_file_t *file[2];
apr_off_t current_line[2];
char buffer[2][4096];
apr_size_t length[2];
char *curp[2];
apr_off_t hunk_start[2];
apr_off_t hunk_length[2];
svn_stringbuf_t *hunk;
svn_boolean_t show_c_function;
apr_array_header_t *extra_skip_match;
svn_stringbuf_t *extra_context;
char hunk_extra_context[SVN_DIFF__EXTRA_CONTEXT_LENGTH + 1];
apr_pool_t *pool;
} svn_diff__file_output_baton_t;
typedef enum svn_diff__file_output_unified_type_e
{
svn_diff__file_output_unified_skip,
svn_diff__file_output_unified_context,
svn_diff__file_output_unified_delete,
svn_diff__file_output_unified_insert
} svn_diff__file_output_unified_type_e;
static svn_error_t *
output_unified_line(svn_diff__file_output_baton_t *baton,
svn_diff__file_output_unified_type_e type, int idx)
{
char *curp;
char *eol;
apr_size_t length;
svn_error_t *err;
svn_boolean_t bytes_processed = FALSE;
svn_boolean_t had_cr = FALSE;
svn_boolean_t collect_extra = FALSE;
length = baton->length[idx];
curp = baton->curp[idx];
baton->current_line[idx]++;
if (length == 0 && apr_file_eof(baton->file[idx]))
{
return SVN_NO_ERROR;
}
do
{
if (length > 0)
{
if (!bytes_processed)
{
switch (type)
{
case svn_diff__file_output_unified_context:
svn_stringbuf_appendcstr(baton->hunk, baton->context_str);
baton->hunk_length[0]++;
baton->hunk_length[1]++;
break;
case svn_diff__file_output_unified_delete:
svn_stringbuf_appendcstr(baton->hunk, baton->delete_str);
baton->hunk_length[0]++;
break;
case svn_diff__file_output_unified_insert:
svn_stringbuf_appendcstr(baton->hunk, baton->insert_str);
baton->hunk_length[1]++;
break;
default:
break;
}
if (baton->show_c_function
&& (type == svn_diff__file_output_unified_skip
|| type == svn_diff__file_output_unified_context)
&& (svn_ctype_isalpha(*curp) || *curp == '$' || *curp == '_')
&& !svn_cstring_match_glob_list(curp,
baton->extra_skip_match))
{
svn_stringbuf_setempty(baton->extra_context);
collect_extra = TRUE;
}
}
eol = svn_eol__find_eol_start(curp, length);
if (eol != NULL)
{
apr_size_t len;
had_cr = (*eol == '\r');
eol++;
len = (apr_size_t)(eol - curp);
if (! had_cr || len < length)
{
if (had_cr && *eol == '\n')
{
++eol;
++len;
}
length -= len;
if (type != svn_diff__file_output_unified_skip)
{
svn_stringbuf_appendbytes(baton->hunk, curp, len);
}
if (collect_extra)
{
svn_stringbuf_appendbytes(baton->extra_context,
curp, len);
}
baton->curp[idx] = eol;
baton->length[idx] = length;
err = SVN_NO_ERROR;
break;
}
}
if (type != svn_diff__file_output_unified_skip)
{
svn_stringbuf_appendbytes(baton->hunk, curp, length);
}
if (collect_extra)
{
svn_stringbuf_appendbytes(baton->extra_context, curp, length);
}
bytes_processed = TRUE;
}
curp = baton->buffer[idx];
length = sizeof(baton->buffer[idx]);
err = svn_io_file_read(baton->file[idx], curp, &length, baton->pool);
if (had_cr)
{
if (! err && length > 0 && *curp == '\n')
{
if (type != svn_diff__file_output_unified_skip)
{
svn_stringbuf_appendbyte(baton->hunk, *curp);
}
++curp;
--length;
}
baton->curp[idx] = curp;
baton->length[idx] = length;
break;
}
}
while (! err);
if (err && ! APR_STATUS_IS_EOF(err->apr_err))
return err;
if (err && APR_STATUS_IS_EOF(err->apr_err))
{
svn_error_clear(err);
if (bytes_processed && (type != svn_diff__file_output_unified_skip)
&& ! had_cr)
{
const char *out_str;
SVN_ERR(svn_utf_cstring_from_utf8_ex2
(&out_str,
apr_psprintf(baton->pool,
APR_EOL_STR "\\ %s" APR_EOL_STR,
_("No newline at end of file")),
baton->header_encoding, baton->pool));
svn_stringbuf_appendcstr(baton->hunk, out_str);
}
baton->length[idx] = 0;
}
return SVN_NO_ERROR;
}
static svn_error_t *
output_unified_flush_hunk(svn_diff__file_output_baton_t *baton)
{
apr_off_t target_line;
apr_size_t hunk_len;
int i;
if (svn_stringbuf_isempty(baton->hunk))
{
return SVN_NO_ERROR;
}
target_line = baton->hunk_start[0] + baton->hunk_length[0]
+ SVN_DIFF__UNIFIED_CONTEXT_SIZE;
while (baton->current_line[0] < target_line)
{
SVN_ERR(output_unified_line
(baton, svn_diff__file_output_unified_context, 0));
}
for (i = 0; i < 2; i++)
{
if (baton->hunk_length[i] > 0)
baton->hunk_start[i]++;
}
SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
baton->header_encoding,
baton->pool,
"@@ -%" APR_OFF_T_FMT,
baton->hunk_start[0]));
if (baton->hunk_length[0] != 1)
{
SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
baton->header_encoding,
baton->pool, ",%" APR_OFF_T_FMT,
baton->hunk_length[0]));
}
SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
baton->header_encoding,
baton->pool, " +%" APR_OFF_T_FMT,
baton->hunk_start[1]));
if (baton->hunk_length[1] != 1)
{
SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
baton->header_encoding,
baton->pool, ",%" APR_OFF_T_FMT,
baton->hunk_length[1]));
}
SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
baton->header_encoding,
baton->pool, " @@%s%s" APR_EOL_STR,
baton->hunk_extra_context[0]
? " " : "",
baton->hunk_extra_context));
hunk_len = baton->hunk->len;
SVN_ERR(svn_stream_write(baton->output_stream, baton->hunk->data,
&hunk_len));
baton->hunk_length[0] = 0;
baton->hunk_length[1] = 0;
svn_stringbuf_setempty(baton->hunk);
return SVN_NO_ERROR;
}
static svn_error_t *
output_unified_diff_modified(void *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__file_output_baton_t *output_baton = baton;
apr_off_t target_line[2];
int i;
target_line[0] = original_start >= SVN_DIFF__UNIFIED_CONTEXT_SIZE
? original_start - SVN_DIFF__UNIFIED_CONTEXT_SIZE : 0;
target_line[1] = modified_start;
if (output_baton->current_line[0] < target_line[0]
&& (output_baton->hunk_start[0] + output_baton->hunk_length[0]
+ SVN_DIFF__UNIFIED_CONTEXT_SIZE < target_line[0]
|| output_baton->hunk_length[0] == 0))
{
SVN_ERR(output_unified_flush_hunk(output_baton));
output_baton->hunk_start[0] = target_line[0];
output_baton->hunk_start[1] = target_line[1] + target_line[0]
- original_start;
while (output_baton->current_line[0] < target_line[0])
{
SVN_ERR(output_unified_line(output_baton,
svn_diff__file_output_unified_skip, 0));
}
if (output_baton->show_c_function)
{
int p;
const char *invalid_character;
strncpy(output_baton->hunk_extra_context,
output_baton->extra_context->data,
SVN_DIFF__EXTRA_CONTEXT_LENGTH);
p = strlen(output_baton->hunk_extra_context);
while (p > 0
&& svn_ctype_isspace(output_baton->hunk_extra_context[p - 1]))
{
output_baton->hunk_extra_context[--p] = '\0';
}
invalid_character =
svn_utf__last_valid(output_baton->hunk_extra_context,
SVN_DIFF__EXTRA_CONTEXT_LENGTH);
for (p = invalid_character - output_baton->hunk_extra_context;
p < SVN_DIFF__EXTRA_CONTEXT_LENGTH; p++)
{
output_baton->hunk_extra_context[p] = '\0';
}
}
}
while (output_baton->current_line[1] < target_line[1])
{
SVN_ERR(output_unified_line(output_baton,
svn_diff__file_output_unified_skip, 1));
}
while (output_baton->current_line[0] < original_start)
{
SVN_ERR(output_unified_line(output_baton,
svn_diff__file_output_unified_context, 0));
}
target_line[0] = original_start + original_length;
target_line[1] = modified_start + modified_length;
for (i = 0; i < 2; i++)
{
while (output_baton->current_line[i] < target_line[i])
{
SVN_ERR(output_unified_line
(output_baton,
i == 0 ? svn_diff__file_output_unified_delete
: svn_diff__file_output_unified_insert, i));
}
}
return SVN_NO_ERROR;
}
static svn_error_t *
output_unified_default_hdr(const char **header, const char *path,
apr_pool_t *pool)
{
apr_finfo_t file_info;
apr_time_exp_t exploded_time;
char time_buffer[64];
apr_size_t time_len;
const char *utf8_timestr;
SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_MTIME, pool));
apr_time_exp_lt(&exploded_time, file_info.mtime);
apr_strftime(time_buffer, &time_len, sizeof(time_buffer) - 1,
_("%a %b %e %H:%M:%S %Y"), &exploded_time);
SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, time_buffer, pool));
*header = apr_psprintf(pool, "%s\t%s", path, utf8_timestr);
return SVN_NO_ERROR;
}
static const svn_diff_output_fns_t svn_diff__file_output_unified_vtable =
{
NULL,
output_unified_diff_modified,
NULL,
NULL,
NULL
};
svn_error_t *
svn_diff_file_output_unified3(svn_stream_t *output_stream,
svn_diff_t *diff,
const char *original_path,
const char *modified_path,
const char *original_header,
const char *modified_header,
const char *header_encoding,
const char *relative_to_dir,
svn_boolean_t show_c_function,
apr_pool_t *pool)
{
svn_diff__file_output_baton_t baton;
if (svn_diff_contains_diffs(diff))
{
const char **c;
int i;
memset(&baton, 0, sizeof(baton));
baton.output_stream = output_stream;
baton.pool = pool;
baton.header_encoding = header_encoding;
baton.path[0] = original_path;
baton.path[1] = modified_path;
baton.hunk = svn_stringbuf_create("", pool);
baton.show_c_function = show_c_function;
baton.extra_context = svn_stringbuf_create("", pool);
baton.extra_skip_match = apr_array_make(pool, 3, sizeof(char **));
c = apr_array_push(baton.extra_skip_match);
*c = "public:*";
c = apr_array_push(baton.extra_skip_match);
*c = "private:*";
c = apr_array_push(baton.extra_skip_match);
*c = "protected:*";
SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.context_str, " ",
header_encoding, pool));
SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.delete_str, "-",
header_encoding, pool));
SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.insert_str, "+",
header_encoding, pool));
if (relative_to_dir)
{
const char *child_path;
if (! original_header)
{
child_path = svn_dirent_is_child(relative_to_dir,
original_path, pool);
if (child_path)
original_path = child_path;
else
return svn_error_createf(
SVN_ERR_BAD_RELATIVE_PATH, NULL,
_("Path '%s' must be an immediate child of "
"the directory '%s'"),
svn_dirent_local_style(original_path, pool),
svn_dirent_local_style(relative_to_dir,
pool));
}
if (! modified_header)
{
child_path = svn_dirent_is_child(relative_to_dir,
modified_path, pool);
if (child_path)
modified_path = child_path;
else
return svn_error_createf(
SVN_ERR_BAD_RELATIVE_PATH, NULL,
_("Path '%s' must be an immediate child of "
"the directory '%s'"),
svn_dirent_local_style(modified_path, pool),
svn_dirent_local_style(relative_to_dir,
pool));
}
}
for (i = 0; i < 2; i++)
{
SVN_ERR(svn_io_file_open(&baton.file[i], baton.path[i],
APR_READ, APR_OS_DEFAULT, pool));
}
if (original_header == NULL)
{
SVN_ERR(output_unified_default_hdr
(&original_header, original_path, pool));
}
if (modified_header == NULL)
{
SVN_ERR(output_unified_default_hdr
(&modified_header, modified_path, pool));
}
SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, pool,
"--- %s" APR_EOL_STR
"+++ %s" APR_EOL_STR,
original_header, modified_header));
SVN_ERR(svn_diff_output(diff, &baton,
&svn_diff__file_output_unified_vtable));
SVN_ERR(output_unified_flush_hunk(&baton));
for (i = 0; i < 2; i++)
{
SVN_ERR(svn_io_file_close(baton.file[i], pool));
}
}
return SVN_NO_ERROR;
}
typedef struct context_saver_t {
svn_stream_t *stream;
const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
apr_size_t next_slot;
apr_size_t total_written;
} context_saver_t;
static svn_error_t *
context_saver_stream_write(void *baton,
const char *data,
apr_size_t *len)
{
context_saver_t *cs = baton;
cs->data[cs->next_slot] = data;
cs->len[cs->next_slot] = *len;
cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
cs->total_written++;
return SVN_NO_ERROR;
}
typedef struct svn_diff3__file_output_baton_t
{
svn_stream_t *output_stream;
const char *path[3];
apr_off_t current_line[3];
char *buffer[3];
char *endp[3];
char *curp[3];
const char *conflict_modified;
const char *conflict_original;
const char *conflict_separator;
const char *conflict_latest;
const char *marker_eol;
svn_diff_conflict_display_style_t conflict_style;
svn_stream_t *real_output_stream;
context_saver_t *context_saver;
apr_pool_t *pool;
} svn_diff3__file_output_baton_t;
static svn_error_t *
flush_context_saver(context_saver_t *cs,
svn_stream_t *output_stream)
{
int i;
for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
{
int slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
if (cs->data[slot])
{
apr_size_t len = cs->len[slot];
SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));
}
}
return SVN_NO_ERROR;
}
static void
make_context_saver(svn_diff3__file_output_baton_t *fob)
{
context_saver_t *cs;
svn_pool_clear(fob->pool);
cs = apr_pcalloc(fob->pool, sizeof(*cs));
cs->stream = svn_stream_empty(fob->pool);
svn_stream_set_baton(cs->stream, cs);
svn_stream_set_write(cs->stream, context_saver_stream_write);
fob->context_saver = cs;
fob->output_stream = cs->stream;
}
struct trailing_context_printer {
apr_size_t lines_to_print;
svn_diff3__file_output_baton_t *fob;
};
static svn_error_t *
trailing_context_printer_write(void *baton,
const char *data,
apr_size_t *len)
{
struct trailing_context_printer *tcp = baton;
SVN_ERR_ASSERT(tcp->lines_to_print > 0);
SVN_ERR(svn_stream_write(tcp->fob->real_output_stream, data, len));
tcp->lines_to_print--;
if (tcp->lines_to_print == 0)
make_context_saver(tcp->fob);
return SVN_NO_ERROR;
}
static void
make_trailing_context_printer(svn_diff3__file_output_baton_t *btn)
{
struct trailing_context_printer *tcp;
svn_stream_t *s;
svn_pool_clear(btn->pool);
tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
tcp->fob = btn;
s = svn_stream_empty(btn->pool);
svn_stream_set_baton(s, tcp);
svn_stream_set_write(s, trailing_context_printer_write);
btn->output_stream = s;
}
typedef enum svn_diff3__file_output_type_e
{
svn_diff3__file_output_skip,
svn_diff3__file_output_normal
} svn_diff3__file_output_type_e;
static svn_error_t *
output_line(svn_diff3__file_output_baton_t *baton,
svn_diff3__file_output_type_e type, int idx)
{
char *curp;
char *endp;
char *eol;
apr_size_t len;
curp = baton->curp[idx];
endp = baton->endp[idx];
baton->current_line[idx]++;
if (curp == endp)
return SVN_NO_ERROR;
eol = svn_eol__find_eol_start(curp, endp - curp);
if (!eol)
eol = endp;
else
{
svn_boolean_t had_cr = (*eol == '\r');
eol++;
if (had_cr && eol != endp && *eol == '\n')
eol++;
}
if (type != svn_diff3__file_output_skip)
{
len = eol - curp;
SVN_ERR(svn_stream_write(baton->output_stream, curp, &len));
}
baton->curp[idx] = eol;
return SVN_NO_ERROR;
}
static svn_error_t *
output_marker_eol(svn_diff3__file_output_baton_t *btn)
{
apr_size_t len = strlen(btn->marker_eol);
return svn_stream_write(btn->output_stream, btn->marker_eol, &len);
}
static svn_error_t *
output_hunk(void *baton, int idx, apr_off_t target_line,
apr_off_t target_length)
{
svn_diff3__file_output_baton_t *output_baton = baton;
while (output_baton->current_line[idx] < target_line)
{
SVN_ERR(output_line(output_baton, svn_diff3__file_output_skip, idx));
}
target_line += target_length;
while (output_baton->current_line[idx] < target_line)
{
SVN_ERR(output_line(output_baton, svn_diff3__file_output_normal, idx));
}
return SVN_NO_ERROR;
}
static svn_error_t *
output_common(void *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)
{
return output_hunk(baton, 1, modified_start, modified_length);
}
static svn_error_t *
output_diff_modified(void *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)
{
return output_hunk(baton, 1, modified_start, modified_length);
}
static svn_error_t *
output_diff_latest(void *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)
{
return output_hunk(baton, 2, latest_start, latest_length);
}
static svn_error_t *
output_conflict(void *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 *diff);
static const svn_diff_output_fns_t svn_diff3__file_output_vtable =
{
output_common,
output_diff_modified,
output_diff_latest,
output_diff_modified,
output_conflict
};
static svn_error_t *
output_conflict_with_context(svn_diff3__file_output_baton_t *btn,
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)
{
if (btn->output_stream == btn->context_saver->stream)
{
if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
SVN_ERR(svn_stream_printf(btn->real_output_stream, btn->pool, "@@\n"));
SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));
}
btn->output_stream = btn->real_output_stream;
SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
(modified_length == 1
? "%s (%" APR_OFF_T_FMT ")"
: "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
btn->conflict_modified,
modified_start + 1, modified_length));
SVN_ERR(output_marker_eol(btn));
SVN_ERR(output_hunk(btn, 1, modified_start, modified_length));
SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
(original_length == 1
? "%s (%" APR_OFF_T_FMT ")"
: "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
btn->conflict_original,
original_start + 1, original_length));
SVN_ERR(output_marker_eol(btn));
SVN_ERR(output_hunk(btn, 0, original_start, original_length));
SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
"%s%s", btn->conflict_separator, btn->marker_eol));
SVN_ERR(output_hunk(btn, 2, latest_start, latest_length));
SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
(latest_length == 1
? "%s (%" APR_OFF_T_FMT ")"
: "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
btn->conflict_latest,
latest_start + 1, latest_length));
SVN_ERR(output_marker_eol(btn));
make_trailing_context_printer(btn);
return SVN_NO_ERROR;
}
static svn_error_t *
output_conflict(void *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 *diff)
{
svn_diff3__file_output_baton_t *file_baton = baton;
apr_size_t len;
svn_diff_conflict_display_style_t style = file_baton->conflict_style;
if (style == svn_diff_conflict_display_only_conflicts)
return output_conflict_with_context(file_baton,
original_start, original_length,
modified_start, modified_length,
latest_start, latest_length);
if (style == svn_diff_conflict_display_resolved_modified_latest)
{
if (diff)
return svn_diff_output(diff, baton,
&svn_diff3__file_output_vtable);
else
style = svn_diff_conflict_display_modified_latest;
}
if (style == svn_diff_conflict_display_modified_latest ||
style == svn_diff_conflict_display_modified_original_latest)
{
len = strlen(file_baton->conflict_modified);
SVN_ERR(svn_stream_write(file_baton->output_stream,
file_baton->conflict_modified,
&len));
SVN_ERR(output_marker_eol(file_baton));
SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));
if (style == svn_diff_conflict_display_modified_original_latest)
{
len = strlen(file_baton->conflict_original);
SVN_ERR(svn_stream_write(file_baton->output_stream,
file_baton->conflict_original, &len));
SVN_ERR(output_marker_eol(file_baton));
SVN_ERR(output_hunk(baton, 0, original_start, original_length));
}
len = strlen(file_baton->conflict_separator);
SVN_ERR(svn_stream_write(file_baton->output_stream,
file_baton->conflict_separator, &len));
SVN_ERR(output_marker_eol(file_baton));
SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));
len = strlen(file_baton->conflict_latest);
SVN_ERR(svn_stream_write(file_baton->output_stream,
file_baton->conflict_latest, &len));
SVN_ERR(output_marker_eol(file_baton));
}
else if (style == svn_diff_conflict_display_modified)
SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));
else if (style == svn_diff_conflict_display_latest)
SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));
else
SVN_ERR_MALFUNCTION();
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff_file_output_merge2(svn_stream_t *output_stream,
svn_diff_t *diff,
const char *original_path,
const char *modified_path,
const char *latest_path,
const char *conflict_original,
const char *conflict_modified,
const char *conflict_latest,
const char *conflict_separator,
svn_diff_conflict_display_style_t style,
apr_pool_t *pool)
{
svn_diff3__file_output_baton_t baton;
apr_file_t *file[3];
apr_off_t size;
int idx;
#if APR_HAS_MMAP
apr_mmap_t *mm[3] = { 0 };
#endif
const char *eol;
svn_boolean_t conflicts_only =
(style == svn_diff_conflict_display_only_conflicts);
memset(&baton, 0, sizeof(baton));
if (conflicts_only)
{
baton.pool = svn_pool_create(pool);
make_context_saver(&baton);
baton.real_output_stream = output_stream;
}
else
baton.output_stream = output_stream;
baton.path[0] = original_path;
baton.path[1] = modified_path;
baton.path[2] = latest_path;
SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_modified,
conflict_modified ? conflict_modified
: apr_psprintf(pool, "<<<<<<< %s",
modified_path),
pool));
SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_original,
conflict_original ? conflict_original
: apr_psprintf(pool, "||||||| %s",
original_path),
pool));
SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_separator,
conflict_separator ? conflict_separator
: "=======", pool));
SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_latest,
conflict_latest ? conflict_latest
: apr_psprintf(pool, ">>>>>>> %s",
latest_path),
pool));
baton.conflict_style = style;
for (idx = 0; idx < 3; idx++)
{
SVN_ERR(map_or_read_file(&file[idx],
MMAP_T_ARG(mm[idx])
&baton.buffer[idx], &size,
baton.path[idx], pool));
baton.curp[idx] = baton.buffer[idx];
baton.endp[idx] = baton.buffer[idx];
if (baton.endp[idx])
baton.endp[idx] += size;
}
eol = svn_eol__detect_eol(baton.buffer[1], baton.endp[1] - baton.buffer[1],
NULL);
if (! eol)
eol = APR_EOL_STR;
baton.marker_eol = eol;
SVN_ERR(svn_diff_output(diff, &baton,
&svn_diff3__file_output_vtable));
for (idx = 0; idx < 3; idx++)
{
#if APR_HAS_MMAP
if (mm[idx])
{
apr_status_t rv = apr_mmap_delete(mm[idx]);
if (rv != APR_SUCCESS)
{
return svn_error_wrap_apr(rv, _("Failed to delete mmap '%s'"),
baton.path[idx]);
}
}
#endif
if (file[idx])
{
SVN_ERR(svn_io_file_close(file[idx], pool));
}
}
if (conflicts_only)
svn_pool_destroy(baton.pool);
return SVN_NO_ERROR;
}