#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include "svn_hash.h"
#include "svn_types.h"
#include "svn_error.h"
#include "svn_io.h"
#include "svn_pools.h"
#include "svn_props.h"
#include "svn_string.h"
#include "svn_utf.h"
#include "svn_dirent_uri.h"
#include "svn_diff.h"
#include "svn_ctype.h"
#include "svn_mergeinfo.h"
#include "private/svn_eol_private.h"
#include "private/svn_dep_compat.h"
#include "private/svn_diff_private.h"
#include "private/svn_sorts_private.h"
#include "diff.h"
#include "svn_private_config.h"
#define starts_with(str, start) \
(strncmp((str), (start), strlen(start)) == 0)
#define STRLEN_LITERAL(str) (sizeof(str) - 1)
struct svn_diff__hunk_range {
apr_off_t start;
apr_off_t end;
apr_off_t current;
};
struct svn_diff_hunk_t {
const svn_patch_t *patch;
apr_file_t *apr_file;
struct svn_diff__hunk_range diff_text_range;
struct svn_diff__hunk_range original_text_range;
struct svn_diff__hunk_range modified_text_range;
svn_linenum_t original_start;
svn_linenum_t original_length;
svn_linenum_t modified_start;
svn_linenum_t modified_length;
svn_linenum_t leading_context;
svn_linenum_t trailing_context;
svn_boolean_t original_no_final_eol;
svn_boolean_t modified_no_final_eol;
svn_linenum_t original_fuzz;
svn_linenum_t modified_fuzz;
};
struct svn_diff_binary_patch_t {
const svn_patch_t *patch;
apr_file_t *apr_file;
apr_off_t src_start;
apr_off_t src_end;
svn_filesize_t src_filesize;
apr_off_t dst_start;
apr_off_t dst_end;
svn_filesize_t dst_filesize;
};
static svn_error_t *
add_or_delete_single_line(svn_diff_hunk_t **hunk_out,
const char *line,
const svn_patch_t *patch,
svn_boolean_t add,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_diff_hunk_t *hunk = apr_pcalloc(result_pool, sizeof(*hunk));
static const char *hunk_header[] = { "@@ -1 +0,0 @@\n", "@@ -0,0 +1 @@\n" };
const apr_size_t header_len = strlen(hunk_header[add]);
const apr_size_t len = strlen(line);
const apr_size_t end = header_len + (1 + len);
svn_stringbuf_t *buf = svn_stringbuf_create_ensure(end + 1, scratch_pool);
hunk->patch = patch;
hunk->diff_text_range.start = header_len;
hunk->diff_text_range.current = header_len;
if (add)
{
hunk->original_text_range.start = 0;
hunk->original_text_range.current = 0;
hunk->original_text_range.end = 0;
hunk->original_no_final_eol = FALSE;
hunk->modified_text_range.start = header_len;
hunk->modified_text_range.current = header_len;
hunk->modified_text_range.end = end;
hunk->modified_no_final_eol = TRUE;
hunk->original_start = 0;
hunk->original_length = 0;
hunk->modified_start = 1;
hunk->modified_length = 1;
}
else
{
hunk->original_text_range.start = header_len;
hunk->original_text_range.current = header_len;
hunk->original_text_range.end = end;
hunk->original_no_final_eol = TRUE;
hunk->modified_text_range.start = 0;
hunk->modified_text_range.current = 0;
hunk->modified_text_range.end = 0;
hunk->modified_no_final_eol = FALSE;
hunk->original_start = 1;
hunk->original_length = 1;
hunk->modified_start = 0;
hunk->modified_length = 0;
}
hunk->leading_context = 0;
hunk->trailing_context = 0;
svn_stringbuf_appendbytes(buf, hunk_header[add], header_len);
svn_stringbuf_appendbyte(buf, add ? '+' : '-');
svn_stringbuf_appendbytes(buf, line, len);
svn_stringbuf_appendbyte(buf, '\n');
svn_stringbuf_appendcstr(buf, "\\ No newline at end of hunk\n");
hunk->diff_text_range.end = buf->len;
SVN_ERR(svn_io_open_unique_file3(&hunk->apr_file, NULL ,
NULL ,
svn_io_file_del_on_pool_cleanup,
result_pool, scratch_pool));
SVN_ERR(svn_io_file_write_full(hunk->apr_file,
buf->data, buf->len,
NULL, scratch_pool));
*hunk_out = hunk;
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff_hunk__create_adds_single_line(svn_diff_hunk_t **hunk_out,
const char *line,
const svn_patch_t *patch,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
SVN_ERR(add_or_delete_single_line(hunk_out, line, patch,
(!patch->reverse),
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff_hunk__create_deletes_single_line(svn_diff_hunk_t **hunk_out,
const char *line,
const svn_patch_t *patch,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
SVN_ERR(add_or_delete_single_line(hunk_out, line, patch,
patch->reverse,
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
void
svn_diff_hunk_reset_diff_text(svn_diff_hunk_t *hunk)
{
hunk->diff_text_range.current = hunk->diff_text_range.start;
}
void
svn_diff_hunk_reset_original_text(svn_diff_hunk_t *hunk)
{
if (hunk->patch->reverse)
hunk->modified_text_range.current = hunk->modified_text_range.start;
else
hunk->original_text_range.current = hunk->original_text_range.start;
}
void
svn_diff_hunk_reset_modified_text(svn_diff_hunk_t *hunk)
{
if (hunk->patch->reverse)
hunk->original_text_range.current = hunk->original_text_range.start;
else
hunk->modified_text_range.current = hunk->modified_text_range.start;
}
svn_linenum_t
svn_diff_hunk_get_original_start(const svn_diff_hunk_t *hunk)
{
return hunk->patch->reverse ? hunk->modified_start : hunk->original_start;
}
svn_linenum_t
svn_diff_hunk_get_original_length(const svn_diff_hunk_t *hunk)
{
return hunk->patch->reverse ? hunk->modified_length : hunk->original_length;
}
svn_linenum_t
svn_diff_hunk_get_modified_start(const svn_diff_hunk_t *hunk)
{
return hunk->patch->reverse ? hunk->original_start : hunk->modified_start;
}
svn_linenum_t
svn_diff_hunk_get_modified_length(const svn_diff_hunk_t *hunk)
{
return hunk->patch->reverse ? hunk->original_length : hunk->modified_length;
}
svn_linenum_t
svn_diff_hunk_get_leading_context(const svn_diff_hunk_t *hunk)
{
return hunk->leading_context;
}
svn_linenum_t
svn_diff_hunk_get_trailing_context(const svn_diff_hunk_t *hunk)
{
return hunk->trailing_context;
}
svn_linenum_t
svn_diff_hunk__get_fuzz_penalty(const svn_diff_hunk_t *hunk)
{
return hunk->patch->reverse ? hunk->original_fuzz : hunk->modified_fuzz;
}
struct base85_baton_t
{
apr_file_t *file;
apr_pool_t *iterpool;
char buffer[52];
apr_off_t next_pos;
apr_off_t end_pos;
apr_size_t buf_size;
apr_size_t buf_pos;
svn_boolean_t done;
};
static svn_error_t *
read_handler_base85(void *baton, char *buffer, apr_size_t *len)
{
struct base85_baton_t *b85b = baton;
apr_pool_t *iterpool = b85b->iterpool;
apr_size_t remaining = *len;
char *dest = buffer;
svn_pool_clear(iterpool);
if (b85b->done)
{
*len = 0;
return SVN_NO_ERROR;
}
while (remaining && (b85b->buf_size > b85b->buf_pos
|| b85b->next_pos < b85b->end_pos))
{
svn_stringbuf_t *line;
svn_boolean_t at_eof;
apr_size_t available = b85b->buf_size - b85b->buf_pos;
if (available)
{
apr_size_t n = (remaining < available) ? remaining : available;
memcpy(dest, b85b->buffer + b85b->buf_pos, n);
dest += n;
remaining -= n;
b85b->buf_pos += n;
if (!remaining)
return SVN_NO_ERROR;
}
if (b85b->next_pos >= b85b->end_pos)
break;
SVN_ERR(svn_io_file_seek(b85b->file, APR_SET, &b85b->next_pos,
iterpool));
SVN_ERR(svn_io_file_readline(b85b->file, &line, NULL, &at_eof,
APR_SIZE_MAX, iterpool, iterpool));
if (at_eof)
b85b->next_pos = b85b->end_pos;
else
{
SVN_ERR(svn_io_file_get_offset(&b85b->next_pos, b85b->file,
iterpool));
}
if (line->len && line->data[0] >= 'A' && line->data[0] <= 'Z')
b85b->buf_size = line->data[0] - 'A' + 1;
else if (line->len && line->data[0] >= 'a' && line->data[0] <= 'z')
b85b->buf_size = line->data[0] - 'a' + 26 + 1;
else
return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL,
_("Unexpected data in base85 section"));
if (b85b->buf_size < 52)
b85b->next_pos = b85b->end_pos;
SVN_ERR(svn_diff__base85_decode_line(b85b->buffer, b85b->buf_size,
line->data + 1, line->len - 1,
iterpool));
b85b->buf_pos = 0;
}
*len -= remaining;
b85b->done = TRUE;
return SVN_NO_ERROR;
}
static svn_error_t *
close_handler_base85(void *baton)
{
struct base85_baton_t *b85b = baton;
svn_pool_destroy(b85b->iterpool);
return SVN_NO_ERROR;
}
static svn_stream_t *
get_base85_data_stream(apr_file_t *file,
apr_off_t start_pos,
apr_off_t end_pos,
apr_pool_t *result_pool)
{
struct base85_baton_t *b85b = apr_pcalloc(result_pool, sizeof(*b85b));
svn_stream_t *base85s = svn_stream_create(b85b, result_pool);
b85b->file = file;
b85b->iterpool = svn_pool_create(result_pool);
b85b->next_pos = start_pos;
b85b->end_pos = end_pos;
svn_stream_set_read2(base85s, NULL ,
read_handler_base85);
svn_stream_set_close(base85s, close_handler_base85);
return base85s;
}
struct length_verify_baton_t
{
svn_stream_t *inner;
svn_filesize_t remaining;
};
static svn_error_t *
read_handler_length_verify(void *baton, char *buffer, apr_size_t *len)
{
struct length_verify_baton_t *lvb = baton;
apr_size_t requested_len = *len;
SVN_ERR(svn_stream_read_full(lvb->inner, buffer, len));
if (*len > lvb->remaining)
return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL,
_("Base85 data expands to longer than declared "
"filesize"));
else if (requested_len > *len && *len != lvb->remaining)
return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL,
_("Base85 data expands to smaller than declared "
"filesize"));
lvb->remaining -= *len;
return SVN_NO_ERROR;
}
static svn_error_t *
close_handler_length_verify(void *baton)
{
struct length_verify_baton_t *lvb = baton;
return svn_error_trace(svn_stream_close(lvb->inner));
}
static svn_stream_t *
get_verify_length_stream(svn_stream_t *inner,
svn_filesize_t expected_size,
apr_pool_t *result_pool)
{
struct length_verify_baton_t *lvb = apr_palloc(result_pool, sizeof(*lvb));
svn_stream_t *len_stream = svn_stream_create(lvb, result_pool);
lvb->inner = inner;
lvb->remaining = expected_size;
svn_stream_set_read2(len_stream, NULL ,
read_handler_length_verify);
svn_stream_set_close(len_stream, close_handler_length_verify);
return len_stream;
}
svn_stream_t *
svn_diff_get_binary_diff_original_stream(const svn_diff_binary_patch_t *bpatch,
apr_pool_t *result_pool)
{
svn_stream_t *s = get_base85_data_stream(bpatch->apr_file, bpatch->src_start,
bpatch->src_end, result_pool);
s = svn_stream_compressed(s, result_pool);
return get_verify_length_stream(s, bpatch->src_filesize, result_pool);
}
svn_stream_t *
svn_diff_get_binary_diff_result_stream(const svn_diff_binary_patch_t *bpatch,
apr_pool_t *result_pool)
{
svn_stream_t *s = get_base85_data_stream(bpatch->apr_file, bpatch->dst_start,
bpatch->dst_end, result_pool);
s = svn_stream_compressed(s, result_pool);
return get_verify_length_stream(s, bpatch->dst_filesize, result_pool);
}
static svn_boolean_t
parse_offset(svn_linenum_t *offset, const char *number)
{
svn_error_t *err;
apr_uint64_t val;
err = svn_cstring_strtoui64(&val, number, 0, SVN_LINENUM_MAX_VALUE, 10);
if (err)
{
svn_error_clear(err);
return FALSE;
}
*offset = (svn_linenum_t)val;
return TRUE;
}
static svn_boolean_t
parse_range(svn_linenum_t *start, svn_linenum_t *length, char *range)
{
char *comma;
if (*range == 0)
return FALSE;
comma = strstr(range, ",");
if (comma)
{
if (strlen(comma + 1) > 0)
{
if (! parse_offset(length, comma + 1))
return FALSE;
*comma = '\0';
}
else
return FALSE;
}
else
{
*length = 1;
}
return parse_offset(start, range);
}
static svn_boolean_t
parse_hunk_header(const char *header, svn_diff_hunk_t *hunk,
const char *atat, apr_pool_t *pool)
{
const char *p;
const char *start;
svn_stringbuf_t *range;
p = header + strlen(atat);
if (*p != ' ')
return FALSE;
p++;
if (*p != '-')
return FALSE;
range = svn_stringbuf_create_ensure(31, pool);
start = ++p;
while (*p && *p != ' ')
{
p++;
}
if (*p != ' ')
return FALSE;
svn_stringbuf_appendbytes(range, start, p - start);
if (! parse_range(&hunk->original_start, &hunk->original_length, range->data))
return FALSE;
svn_stringbuf_setempty(range);
p++;
if (*p != '+')
return FALSE;
start = ++p;
while (*p && *p != ' ')
{
p++;
}
if (*p != ' ')
return FALSE;
svn_stringbuf_appendbytes(range, start, p - start);
p++;
if (! starts_with(p, atat))
return FALSE;
if (! parse_range(&hunk->modified_start, &hunk->modified_length, range->data))
return FALSE;
return TRUE;
}
static svn_error_t *
hunk_readline_original_or_modified(apr_file_t *file,
struct svn_diff__hunk_range *range,
svn_stringbuf_t **stringbuf,
const char **eol,
svn_boolean_t *eof,
char verboten,
svn_boolean_t no_final_eol,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_size_t max_len;
svn_boolean_t filtered;
apr_off_t pos;
svn_stringbuf_t *str;
const char *eol_p;
apr_pool_t *last_pool;
if (!eol)
eol = &eol_p;
if (range->current >= range->end)
{
*eof = TRUE;
*eol = NULL;
*stringbuf = svn_stringbuf_create_empty(result_pool);
return SVN_NO_ERROR;
}
SVN_ERR(svn_io_file_get_offset(&pos, file, scratch_pool));
SVN_ERR(svn_io_file_seek(file, APR_SET, &range->current, scratch_pool));
last_pool = svn_pool_create(scratch_pool);
do
{
svn_pool_clear(last_pool);
max_len = range->end - range->current;
SVN_ERR(svn_io_file_readline(file, &str, eol, eof, max_len,
last_pool, last_pool));
SVN_ERR(svn_io_file_get_offset(&range->current, file, last_pool));
filtered = (str->data[0] == verboten || str->data[0] == '\\');
}
while (filtered && ! *eof);
if (filtered)
{
*stringbuf = svn_stringbuf_create_ensure(0, result_pool);
*eol = NULL;
}
else if (str->data[0] == '+' || str->data[0] == '-' || str->data[0] == ' ')
{
*stringbuf = svn_stringbuf_create(str->data + 1, result_pool);
}
else
{
*stringbuf = svn_stringbuf_dup(str, result_pool);
}
if (!filtered && *eof && !*eol && *str->data)
{
if (!no_final_eol && eol != &eol_p)
{
apr_off_t start = 0;
SVN_ERR(svn_io_file_seek(file, APR_SET, &start, scratch_pool));
SVN_ERR(svn_io_file_readline(file, &str, eol, NULL, APR_SIZE_MAX,
scratch_pool, scratch_pool));
SVN_ERR_ASSERT(*eol != NULL);
}
*eof = FALSE;
}
SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
svn_pool_destroy(last_pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff_hunk_readline_original_text(svn_diff_hunk_t *hunk,
svn_stringbuf_t **stringbuf,
const char **eol,
svn_boolean_t *eof,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
return svn_error_trace(
hunk_readline_original_or_modified(hunk->apr_file,
hunk->patch->reverse ?
&hunk->modified_text_range :
&hunk->original_text_range,
stringbuf, eol, eof,
hunk->patch->reverse ? '-' : '+',
hunk->patch->reverse
? hunk->modified_no_final_eol
: hunk->original_no_final_eol,
result_pool, scratch_pool));
}
svn_error_t *
svn_diff_hunk_readline_modified_text(svn_diff_hunk_t *hunk,
svn_stringbuf_t **stringbuf,
const char **eol,
svn_boolean_t *eof,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
return svn_error_trace(
hunk_readline_original_or_modified(hunk->apr_file,
hunk->patch->reverse ?
&hunk->original_text_range :
&hunk->modified_text_range,
stringbuf, eol, eof,
hunk->patch->reverse ? '+' : '-',
hunk->patch->reverse
? hunk->original_no_final_eol
: hunk->modified_no_final_eol,
result_pool, scratch_pool));
}
svn_error_t *
svn_diff_hunk_readline_diff_text(svn_diff_hunk_t *hunk,
svn_stringbuf_t **stringbuf,
const char **eol,
svn_boolean_t *eof,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_stringbuf_t *line;
apr_size_t max_len;
apr_off_t pos;
const char *eol_p;
if (!eol)
eol = &eol_p;
if (hunk->diff_text_range.current >= hunk->diff_text_range.end)
{
*eof = TRUE;
*eol = NULL;
*stringbuf = svn_stringbuf_create_empty(result_pool);
return SVN_NO_ERROR;
}
SVN_ERR(svn_io_file_get_offset(&pos, hunk->apr_file, scratch_pool));
SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET,
&hunk->diff_text_range.current, scratch_pool));
max_len = hunk->diff_text_range.end - hunk->diff_text_range.current;
SVN_ERR(svn_io_file_readline(hunk->apr_file, &line, eol, eof, max_len,
result_pool,
scratch_pool));
SVN_ERR(svn_io_file_get_offset(&hunk->diff_text_range.current,
hunk->apr_file, scratch_pool));
if (*eof && !*eol && *line->data)
{
if (eol != &eol_p)
{
apr_off_t start = 0;
svn_stringbuf_t *str;
SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &start,
scratch_pool));
SVN_ERR(svn_io_file_readline(hunk->apr_file, &str, eol, NULL,
APR_SIZE_MAX,
scratch_pool, scratch_pool));
SVN_ERR_ASSERT(*eol != NULL);
}
*eof = FALSE;
}
SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &pos, scratch_pool));
if (hunk->patch->reverse)
{
if (line->data[0] == '+')
line->data[0] = '-';
else if (line->data[0] == '-')
line->data[0] = '+';
}
*stringbuf = line;
return SVN_NO_ERROR;
}
static svn_error_t *
parse_prop_name(const char **prop_name, const char *header,
const char *indicator, apr_pool_t *result_pool)
{
SVN_ERR(svn_utf_cstring_to_utf8(prop_name,
header + strlen(indicator),
result_pool));
if (**prop_name == '\0')
*prop_name = NULL;
else if (! svn_prop_name_is_valid(*prop_name))
{
svn_stringbuf_t *buf = svn_stringbuf_create(*prop_name, result_pool);
svn_stringbuf_strip_whitespace(buf);
*prop_name = (svn_prop_name_is_valid(buf->data) ? buf->data : NULL);
}
return SVN_NO_ERROR;
}
static svn_error_t *
parse_mergeinfo(svn_boolean_t *found_mergeinfo,
svn_stringbuf_t *line,
svn_diff_hunk_t *hunk,
svn_patch_t *patch,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
char *slash = strchr(line->data, '/');
char *colon = strrchr(line->data, ':');
*found_mergeinfo = FALSE;
if (slash && colon && colon[1] == 'r' && slash < colon)
{
svn_stringbuf_t *input;
svn_mergeinfo_t mergeinfo = NULL;
char *s;
svn_error_t *err;
input = svn_stringbuf_create_ensure(line->len, scratch_pool);
s = slash;
while (s <= colon)
{
svn_stringbuf_appendbyte(input, *s);
s++;
}
s++;
while (s < line->data + line->len)
{
if (svn_ctype_isspace(*s))
break;
svn_stringbuf_appendbyte(input, *s);
s++;
}
err = svn_mergeinfo_parse(&mergeinfo, input->data, result_pool);
if (err && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
{
svn_error_clear(err);
mergeinfo = NULL;
}
else
SVN_ERR(err);
if (mergeinfo)
{
if (hunk->original_length > 0)
{
if (patch->reverse)
{
if (patch->mergeinfo == NULL)
patch->mergeinfo = mergeinfo;
else
SVN_ERR(svn_mergeinfo_merge2(patch->mergeinfo,
mergeinfo,
result_pool,
scratch_pool));
}
else
{
if (patch->reverse_mergeinfo == NULL)
patch->reverse_mergeinfo = mergeinfo;
else
SVN_ERR(svn_mergeinfo_merge2(patch->reverse_mergeinfo,
mergeinfo,
result_pool,
scratch_pool));
}
hunk->original_length--;
}
else if (hunk->modified_length > 0)
{
if (patch->reverse)
{
if (patch->reverse_mergeinfo == NULL)
patch->reverse_mergeinfo = mergeinfo;
else
SVN_ERR(svn_mergeinfo_merge2(patch->reverse_mergeinfo,
mergeinfo,
result_pool,
scratch_pool));
}
else
{
if (patch->mergeinfo == NULL)
patch->mergeinfo = mergeinfo;
else
SVN_ERR(svn_mergeinfo_merge2(patch->mergeinfo,
mergeinfo,
result_pool,
scratch_pool));
}
hunk->modified_length--;
}
*found_mergeinfo = TRUE;
}
}
return SVN_NO_ERROR;
}
static svn_error_t *
parse_next_hunk(svn_diff_hunk_t **hunk,
svn_boolean_t *is_property,
const char **prop_name,
svn_diff_operation_kind_t *prop_operation,
svn_patch_t *patch,
apr_file_t *apr_file,
svn_boolean_t ignore_whitespace,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
static const char * const minus = "--- ";
static const char * const text_atat = "@@";
static const char * const prop_atat = "##";
svn_stringbuf_t *line;
svn_boolean_t eof, in_hunk, hunk_seen;
apr_off_t pos, last_line;
apr_off_t start, end;
apr_off_t original_end;
apr_off_t modified_end;
svn_boolean_t original_no_final_eol = FALSE;
svn_boolean_t modified_no_final_eol = FALSE;
svn_linenum_t original_lines;
svn_linenum_t modified_lines;
svn_linenum_t leading_context;
svn_linenum_t trailing_context;
svn_boolean_t changed_line_seen;
enum {
noise_line,
original_line,
modified_line,
context_line
} last_line_type;
apr_pool_t *iterpool;
*prop_operation = svn_diff_op_unchanged;
*prop_name = NULL;
*is_property = FALSE;
if (apr_file_eof(apr_file) == APR_EOF)
{
*hunk = NULL;
return SVN_NO_ERROR;
}
in_hunk = FALSE;
hunk_seen = FALSE;
leading_context = 0;
trailing_context = 0;
changed_line_seen = FALSE;
original_end = 0;
modified_end = 0;
*hunk = apr_pcalloc(result_pool, sizeof(**hunk));
SVN_ERR(svn_io_file_get_offset(&pos, apr_file, scratch_pool));
last_line_type = noise_line;
iterpool = svn_pool_create(scratch_pool);
do
{
svn_pool_clear(iterpool);
last_line = pos;
SVN_ERR(svn_io_file_readline(apr_file, &line, NULL, &eof, APR_SIZE_MAX,
iterpool, iterpool));
SVN_ERR(svn_io_file_get_offset(&pos, apr_file, iterpool));
if (line->data[0] == '\\')
{
if (in_hunk)
{
char eolbuf[2];
apr_size_t len;
apr_off_t off;
apr_off_t hunk_text_end;
off = last_line - 2;
SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &off, iterpool));
len = sizeof(eolbuf);
SVN_ERR(svn_io_file_read_full2(apr_file, eolbuf, len, &len,
&eof, iterpool));
if (eolbuf[0] == '\r' && eolbuf[1] == '\n')
hunk_text_end = last_line - 2;
else if (eolbuf[1] == '\n' || eolbuf[1] == '\r')
hunk_text_end = last_line - 1;
else
hunk_text_end = last_line;
if (last_line_type == original_line && original_end == 0)
original_end = hunk_text_end;
else if (last_line_type == modified_line && modified_end == 0)
modified_end = hunk_text_end;
else if (last_line_type == context_line)
{
if (original_end == 0)
original_end = hunk_text_end;
if (modified_end == 0)
modified_end = hunk_text_end;
}
SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &pos, iterpool));
if (last_line_type != modified_line)
original_no_final_eol = TRUE;
if (last_line_type != original_line)
modified_no_final_eol = TRUE;
}
continue;
}
if (in_hunk && *is_property && *prop_name &&
strcmp(*prop_name, SVN_PROP_MERGEINFO) == 0)
{
svn_boolean_t found_mergeinfo;
SVN_ERR(parse_mergeinfo(&found_mergeinfo, line, *hunk, patch,
result_pool, iterpool));
if (found_mergeinfo)
continue;
else
{
in_hunk = FALSE;
}
}
if (in_hunk)
{
char c;
static const char add = '+';
static const char del = '-';
if (! hunk_seen)
{
start = last_line;
}
c = line->data[0];
if (c == ' '
|| ((original_lines > 0 && modified_lines > 0)
&& (
(! eof && line->len == 0)
|| (ignore_whitespace && c != del && c != add))))
{
hunk_seen = TRUE;
if (original_lines > 0)
original_lines--;
else
{
(*hunk)->original_length++;
(*hunk)->original_fuzz++;
}
if (modified_lines > 0)
modified_lines--;
else
{
(*hunk)->modified_length++;
(*hunk)->modified_fuzz++;
}
if (changed_line_seen)
trailing_context++;
else
leading_context++;
last_line_type = context_line;
}
else if (c == del
&& (original_lines > 0 || line->data[1] != del))
{
hunk_seen = TRUE;
changed_line_seen = TRUE;
if (trailing_context > 0)
trailing_context = 0;
if (original_lines > 0)
original_lines--;
else
{
(*hunk)->original_length++;
(*hunk)->original_fuzz++;
}
last_line_type = original_line;
}
else if (c == add
&& (modified_lines > 0 || line->data[1] != add))
{
hunk_seen = TRUE;
changed_line_seen = TRUE;
if (trailing_context > 0)
trailing_context = 0;
if (modified_lines > 0)
modified_lines--;
else
{
(*hunk)->modified_length++;
(*hunk)->modified_fuzz++;
}
last_line_type = modified_line;
}
else
{
if (eof)
{
end = pos;
}
else
{
end = last_line;
}
if (original_end == 0)
original_end = end;
if (modified_end == 0)
modified_end = end;
break;
}
}
else
{
if (starts_with(line->data, text_atat))
{
in_hunk = parse_hunk_header(line->data, *hunk, text_atat,
iterpool);
if (in_hunk)
{
original_lines = (*hunk)->original_length;
modified_lines = (*hunk)->modified_length;
*is_property = FALSE;
}
}
else if (starts_with(line->data, prop_atat))
{
in_hunk = parse_hunk_header(line->data, *hunk, prop_atat,
iterpool);
if (in_hunk)
{
original_lines = (*hunk)->original_length;
modified_lines = (*hunk)->modified_length;
*is_property = TRUE;
}
}
else if (starts_with(line->data, "Added: "))
{
SVN_ERR(parse_prop_name(prop_name, line->data, "Added: ",
result_pool));
if (*prop_name)
*prop_operation = (patch->reverse ? svn_diff_op_deleted
: svn_diff_op_added);
}
else if (starts_with(line->data, "Deleted: "))
{
SVN_ERR(parse_prop_name(prop_name, line->data, "Deleted: ",
result_pool));
if (*prop_name)
*prop_operation = (patch->reverse ? svn_diff_op_added
: svn_diff_op_deleted);
}
else if (starts_with(line->data, "Modified: "))
{
SVN_ERR(parse_prop_name(prop_name, line->data, "Modified: ",
result_pool));
if (*prop_name)
*prop_operation = svn_diff_op_modified;
}
else if (starts_with(line->data, minus)
|| starts_with(line->data, "diff --git "))
break;
}
}
while (! eof || line->len > 0);
svn_pool_destroy(iterpool);
if (! eof)
SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &last_line, scratch_pool));
if (hunk_seen && start < end)
{
if (original_lines)
{
(*hunk)->original_length -= original_lines;
(*hunk)->original_fuzz += original_lines;
}
if (modified_lines)
{
(*hunk)->modified_length -= modified_lines;
(*hunk)->modified_fuzz += modified_lines;
}
(*hunk)->patch = patch;
(*hunk)->apr_file = apr_file;
(*hunk)->leading_context = leading_context;
(*hunk)->trailing_context = trailing_context;
(*hunk)->diff_text_range.start = start;
(*hunk)->diff_text_range.current = start;
(*hunk)->diff_text_range.end = end;
(*hunk)->original_text_range.start = start;
(*hunk)->original_text_range.current = start;
(*hunk)->original_text_range.end = original_end;
(*hunk)->modified_text_range.start = start;
(*hunk)->modified_text_range.current = start;
(*hunk)->modified_text_range.end = modified_end;
(*hunk)->original_no_final_eol = original_no_final_eol;
(*hunk)->modified_no_final_eol = modified_no_final_eol;
}
else
*hunk = NULL;
return SVN_NO_ERROR;
}
static int
compare_hunks(const void *a, const void *b)
{
const svn_diff_hunk_t *ha = *((const svn_diff_hunk_t *const *)a);
const svn_diff_hunk_t *hb = *((const svn_diff_hunk_t *const *)b);
if (ha->original_start < hb->original_start)
return -1;
if (ha->original_start > hb->original_start)
return 1;
return 0;
}
enum parse_state
{
state_start,
state_git_diff_seen,
state_git_tree_seen,
state_git_minus_seen,
state_git_plus_seen,
state_old_mode_seen,
state_git_mode_seen,
state_move_from_seen,
state_copy_from_seen,
state_minus_seen,
state_unidiff_found,
state_git_header_found,
state_binary_patch_found
};
struct transition
{
const char *expected_input;
enum parse_state required_state;
svn_error_t *(*fn)(enum parse_state *new_state, char *input,
svn_patch_t *patch, apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
};
static svn_error_t *
grab_filename(const char **file_name, const char *line, apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *utf8_path;
const char *canon_path;
SVN_ERR(svn_utf_cstring_to_utf8(&utf8_path,
line,
scratch_pool));
canon_path = svn_dirent_canonicalize(utf8_path, scratch_pool);
*file_name = apr_pstrdup(result_pool, canon_path);
return SVN_NO_ERROR;
}
static svn_error_t *
diff_minus(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
char *tab = strchr(line, '\t');
if (tab)
*tab = '\0';
SVN_ERR(grab_filename(&patch->old_filename, line + STRLEN_LITERAL("--- "),
result_pool, scratch_pool));
*new_state = state_minus_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
diff_plus(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
char *tab = strchr(line, '\t');
if (tab)
*tab = '\0';
SVN_ERR(grab_filename(&patch->new_filename, line + STRLEN_LITERAL("+++ "),
result_pool, scratch_pool));
*new_state = state_unidiff_found;
return SVN_NO_ERROR;
}
static svn_error_t *
git_start(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
const char *old_path_start;
char *old_path_end;
const char *new_path_start;
const char *new_path_end;
char *new_path_marker;
const char *old_path_marker;
old_path_marker = strstr(line, " a/");
if (! old_path_marker)
{
*new_state = state_start;
return SVN_NO_ERROR;
}
if (! *(old_path_marker + 3))
{
*new_state = state_start;
return SVN_NO_ERROR;
}
new_path_marker = strstr(old_path_marker, " b/");
if (! new_path_marker)
{
*new_state = state_start;
return SVN_NO_ERROR;
}
if (! *(new_path_marker + 3))
{
*new_state = state_start;
return SVN_NO_ERROR;
}
old_path_start = line + STRLEN_LITERAL("diff --git a/");
new_path_end = line + strlen(line);
new_path_start = old_path_start;
while (TRUE)
{
ptrdiff_t len_old;
ptrdiff_t len_new;
new_path_marker = strstr(new_path_start, " b/");
if (! new_path_marker)
break;
old_path_end = new_path_marker;
new_path_start = new_path_marker + STRLEN_LITERAL(" b/");
if (! *new_path_start)
break;
len_old = old_path_end - old_path_start;
len_new = new_path_end - new_path_start;
if (len_old == len_new
&& ! strncmp(old_path_start, new_path_start, len_old))
{
*old_path_end = '\0';
SVN_ERR(grab_filename(&patch->old_filename, old_path_start,
result_pool, scratch_pool));
SVN_ERR(grab_filename(&patch->new_filename, new_path_start,
result_pool, scratch_pool));
break;
}
}
patch->operation = svn_diff_op_modified;
*new_state = state_git_diff_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
git_minus(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
char *tab = strchr(line, '\t');
if (tab)
*tab = '\0';
if (starts_with(line, "--- /dev/null"))
SVN_ERR(grab_filename(&patch->old_filename, "/dev/null",
result_pool, scratch_pool));
else
SVN_ERR(grab_filename(&patch->old_filename, line + STRLEN_LITERAL("--- a/"),
result_pool, scratch_pool));
*new_state = state_git_minus_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
git_plus(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
char *tab = strchr(line, '\t');
if (tab)
*tab = '\0';
if (starts_with(line, "+++ /dev/null"))
SVN_ERR(grab_filename(&patch->new_filename, "/dev/null",
result_pool, scratch_pool));
else
SVN_ERR(grab_filename(&patch->new_filename, line + STRLEN_LITERAL("+++ b/"),
result_pool, scratch_pool));
*new_state = state_git_header_found;
return SVN_NO_ERROR;
}
static svn_error_t *
parse_git_mode_bits(svn_tristate_t *executable_p,
svn_tristate_t *symlink_p,
const char *mode_str)
{
apr_uint64_t mode;
SVN_ERR(svn_cstring_strtoui64(&mode, mode_str,
0 ,
0777777 ,
010 ));
switch (mode & 0777)
{
case 0644:
*executable_p = svn_tristate_false;
break;
case 0755:
*executable_p = svn_tristate_true;
break;
default:
*executable_p = svn_tristate_unknown;
break;
}
switch (mode & 0170000 )
{
case 0120000:
*symlink_p = svn_tristate_true;
break;
case 0100000:
case 0040000:
*symlink_p = svn_tristate_false;
break;
default:
*symlink_p = svn_tristate_unknown;
break;
}
return SVN_NO_ERROR;
}
static svn_error_t *
git_old_mode(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
SVN_ERR(parse_git_mode_bits(&patch->old_executable_bit,
&patch->old_symlink_bit,
line + STRLEN_LITERAL("old mode ")));
#ifdef SVN_DEBUG
SVN_ERR_ASSERT(patch->old_executable_bit != svn_tristate_unknown);
#endif
*new_state = state_old_mode_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
git_new_mode(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
SVN_ERR(parse_git_mode_bits(&patch->new_executable_bit,
&patch->new_symlink_bit,
line + STRLEN_LITERAL("new mode ")));
#ifdef SVN_DEBUG
SVN_ERR_ASSERT(patch->new_executable_bit != svn_tristate_unknown);
#endif
*new_state = state_git_mode_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
git_index(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
line = strchr(line + STRLEN_LITERAL("index "), ' ');
if (line && patch->new_executable_bit == svn_tristate_unknown
&& patch->new_symlink_bit == svn_tristate_unknown
&& patch->operation != svn_diff_op_added
&& patch->operation != svn_diff_op_deleted)
{
SVN_ERR(parse_git_mode_bits(&patch->new_executable_bit,
&patch->new_symlink_bit,
line + 1));
patch->old_executable_bit = patch->new_executable_bit;
patch->old_symlink_bit = patch->new_symlink_bit;
}
return SVN_NO_ERROR;
}
static svn_error_t *
git_move_from(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
SVN_ERR(grab_filename(&patch->old_filename,
line + STRLEN_LITERAL("rename from "),
result_pool, scratch_pool));
*new_state = state_move_from_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
git_move_to(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
SVN_ERR(grab_filename(&patch->new_filename,
line + STRLEN_LITERAL("rename to "),
result_pool, scratch_pool));
patch->operation = svn_diff_op_moved;
*new_state = state_git_tree_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
git_copy_from(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
SVN_ERR(grab_filename(&patch->old_filename,
line + STRLEN_LITERAL("copy from "),
result_pool, scratch_pool));
*new_state = state_copy_from_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
git_copy_to(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
SVN_ERR(grab_filename(&patch->new_filename, line + STRLEN_LITERAL("copy to "),
result_pool, scratch_pool));
patch->operation = svn_diff_op_copied;
*new_state = state_git_tree_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
git_new_file(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
SVN_ERR(parse_git_mode_bits(&patch->new_executable_bit,
&patch->new_symlink_bit,
line + STRLEN_LITERAL("new file mode ")));
patch->operation = svn_diff_op_added;
*new_state = state_git_tree_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
git_deleted_file(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
SVN_ERR(parse_git_mode_bits(&patch->old_executable_bit,
&patch->old_symlink_bit,
line + STRLEN_LITERAL("deleted file mode ")));
patch->operation = svn_diff_op_deleted;
*new_state = state_git_tree_seen;
return SVN_NO_ERROR;
}
static svn_error_t *
binary_patch_start(enum parse_state *new_state, char *line, svn_patch_t *patch,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
*new_state = state_binary_patch_found;
return SVN_NO_ERROR;
}
static svn_error_t *
add_property_hunk(svn_patch_t *patch, const char *prop_name,
svn_diff_hunk_t *hunk, svn_diff_operation_kind_t operation,
apr_pool_t *result_pool)
{
svn_prop_patch_t *prop_patch;
prop_patch = svn_hash_gets(patch->prop_patches, prop_name);
if (! prop_patch)
{
prop_patch = apr_palloc(result_pool, sizeof(svn_prop_patch_t));
prop_patch->name = prop_name;
prop_patch->operation = operation;
prop_patch->hunks = apr_array_make(result_pool, 1,
sizeof(svn_diff_hunk_t *));
svn_hash_sets(patch->prop_patches, prop_name, prop_patch);
}
APR_ARRAY_PUSH(prop_patch->hunks, svn_diff_hunk_t *) = hunk;
return SVN_NO_ERROR;
}
struct svn_patch_file_t
{
apr_file_t *apr_file;
apr_off_t next_patch_offset;
};
svn_error_t *
svn_diff_open_patch_file(svn_patch_file_t **patch_file,
const char *local_abspath,
apr_pool_t *result_pool)
{
svn_patch_file_t *p;
p = apr_palloc(result_pool, sizeof(*p));
SVN_ERR(svn_io_file_open(&p->apr_file, local_abspath,
APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
result_pool));
p->next_patch_offset = 0;
*patch_file = p;
return SVN_NO_ERROR;
}
static svn_error_t *
parse_hunks(svn_patch_t *patch, apr_file_t *apr_file,
svn_boolean_t ignore_whitespace,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
svn_diff_hunk_t *hunk;
svn_boolean_t is_property;
const char *last_prop_name;
const char *prop_name;
svn_diff_operation_kind_t prop_operation;
apr_pool_t *iterpool;
last_prop_name = NULL;
patch->hunks = apr_array_make(result_pool, 10, sizeof(svn_diff_hunk_t *));
patch->prop_patches = apr_hash_make(result_pool);
iterpool = svn_pool_create(scratch_pool);
do
{
svn_pool_clear(iterpool);
SVN_ERR(parse_next_hunk(&hunk, &is_property, &prop_name, &prop_operation,
patch, apr_file, ignore_whitespace, result_pool,
iterpool));
if (hunk && is_property)
{
if (! prop_name)
prop_name = last_prop_name;
else
last_prop_name = prop_name;
if (strcmp(prop_name, SVN_PROP_MERGEINFO) == 0)
continue;
SVN_ERR(add_property_hunk(patch, prop_name, hunk, prop_operation,
result_pool));
}
else if (hunk)
{
APR_ARRAY_PUSH(patch->hunks, svn_diff_hunk_t *) = hunk;
last_prop_name = NULL;
}
}
while (hunk);
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
static svn_error_t *
parse_binary_patch(svn_patch_t *patch, apr_file_t *apr_file,
svn_boolean_t reverse,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_off_t pos, last_line;
svn_stringbuf_t *line;
svn_boolean_t eof = FALSE;
svn_diff_binary_patch_t *bpatch = apr_pcalloc(result_pool, sizeof(*bpatch));
svn_boolean_t in_blob = FALSE;
svn_boolean_t in_src = FALSE;
bpatch->apr_file = apr_file;
patch->prop_patches = apr_hash_make(result_pool);
SVN_ERR(svn_io_file_get_offset(&pos, apr_file, scratch_pool));
while (!eof)
{
last_line = pos;
SVN_ERR(svn_io_file_readline(apr_file, &line, NULL, &eof, APR_SIZE_MAX,
iterpool, iterpool));
SVN_ERR(svn_io_file_get_offset(&pos, apr_file, iterpool));
if (in_blob)
{
char c = line->data[0];
if (((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
&& line->len <= 66
&& !strchr(line->data, ':')
&& !strchr(line->data, ' '))
{
if (in_src)
bpatch->src_end = pos;
else
bpatch->dst_end = pos;
}
else if (svn_stringbuf_first_non_whitespace(line) < line->len
&& !(in_src && bpatch->src_start < last_line))
{
break;
}
else if (in_src)
{
patch->binary_patch = bpatch;
break;
}
else
{
in_blob = FALSE;
in_src = TRUE;
}
}
else if (starts_with(line->data, "literal "))
{
apr_uint64_t expanded_size;
svn_error_t *err = svn_cstring_strtoui64(&expanded_size,
&line->data[8],
0, APR_UINT64_MAX, 10);
if (err)
{
svn_error_clear(err);
break;
}
if (in_src)
{
bpatch->src_start = pos;
bpatch->src_filesize = expanded_size;
}
else
{
bpatch->dst_start = pos;
bpatch->dst_filesize = expanded_size;
}
in_blob = TRUE;
}
else
break;
}
svn_pool_destroy(iterpool);
if (!eof)
SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &last_line, scratch_pool));
else if (in_src
&& ((bpatch->src_end > bpatch->src_start) || !bpatch->src_filesize))
{
patch->binary_patch = bpatch;
}
if (reverse && patch->binary_patch)
{
apr_off_t tmp_start = bpatch->src_start;
apr_off_t tmp_end = bpatch->src_end;
svn_filesize_t tmp_filesize = bpatch->src_filesize;
bpatch->src_start = bpatch->dst_start;
bpatch->src_end = bpatch->dst_end;
bpatch->src_filesize = bpatch->dst_filesize;
bpatch->dst_start = tmp_start;
bpatch->dst_end = tmp_end;
bpatch->dst_filesize = tmp_filesize;
}
return SVN_NO_ERROR;
}
static struct transition transitions[] =
{
{"--- ", state_start, diff_minus},
{"+++ ", state_minus_seen, diff_plus},
{"diff --git", state_start, git_start},
{"--- a/", state_git_diff_seen, git_minus},
{"--- a/", state_git_mode_seen, git_minus},
{"--- a/", state_git_tree_seen, git_minus},
{"--- /dev/null", state_git_mode_seen, git_minus},
{"--- /dev/null", state_git_tree_seen, git_minus},
{"+++ b/", state_git_minus_seen, git_plus},
{"+++ /dev/null", state_git_minus_seen, git_plus},
{"old mode ", state_git_diff_seen, git_old_mode},
{"new mode ", state_old_mode_seen, git_new_mode},
{"rename from ", state_git_diff_seen, git_move_from},
{"rename from ", state_git_mode_seen, git_move_from},
{"rename to ", state_move_from_seen, git_move_to},
{"copy from ", state_git_diff_seen, git_copy_from},
{"copy from ", state_git_mode_seen, git_copy_from},
{"copy to ", state_copy_from_seen, git_copy_to},
{"new file ", state_git_diff_seen, git_new_file},
{"deleted file ", state_git_diff_seen, git_deleted_file},
{"index ", state_git_diff_seen, git_index},
{"index ", state_git_tree_seen, git_index},
{"index ", state_git_mode_seen, git_index},
{"GIT binary patch", state_git_diff_seen, binary_patch_start},
{"GIT binary patch", state_git_tree_seen, binary_patch_start},
{"GIT binary patch", state_git_mode_seen, binary_patch_start},
};
svn_error_t *
svn_diff_parse_next_patch(svn_patch_t **patch_p,
svn_patch_file_t *patch_file,
svn_boolean_t reverse,
svn_boolean_t ignore_whitespace,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_off_t pos, last_line;
svn_boolean_t eof;
svn_boolean_t line_after_tree_header_read = FALSE;
apr_pool_t *iterpool;
svn_patch_t *patch;
enum parse_state state = state_start;
if (apr_file_eof(patch_file->apr_file) == APR_EOF)
{
*patch_p = NULL;
return SVN_NO_ERROR;
}
patch = apr_pcalloc(result_pool, sizeof(*patch));
patch->old_executable_bit = svn_tristate_unknown;
patch->new_executable_bit = svn_tristate_unknown;
patch->old_symlink_bit = svn_tristate_unknown;
patch->new_symlink_bit = svn_tristate_unknown;
pos = patch_file->next_patch_offset;
SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &pos, scratch_pool));
iterpool = svn_pool_create(scratch_pool);
do
{
svn_stringbuf_t *line;
svn_boolean_t valid_header_line = FALSE;
int i;
svn_pool_clear(iterpool);
last_line = pos;
SVN_ERR(svn_io_file_readline(patch_file->apr_file, &line, NULL, &eof,
APR_SIZE_MAX, iterpool, iterpool));
if (! eof)
{
SVN_ERR(svn_io_file_get_offset(&pos, patch_file->apr_file,
iterpool));
}
for (i = 0; i < (sizeof(transitions) / sizeof(transitions[0])); i++)
{
if (starts_with(line->data, transitions[i].expected_input)
&& state == transitions[i].required_state)
{
SVN_ERR(transitions[i].fn(&state, line->data, patch,
result_pool, iterpool));
valid_header_line = TRUE;
break;
}
}
if (state == state_unidiff_found
|| state == state_git_header_found
|| state == state_binary_patch_found)
{
break;
}
else if ((state == state_git_tree_seen || state == state_git_mode_seen)
&& line_after_tree_header_read
&& !valid_header_line)
{
SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line,
scratch_pool));
break;
}
else if (state == state_git_tree_seen
|| state == state_git_mode_seen)
{
line_after_tree_header_read = TRUE;
}
else if (! valid_header_line && state != state_start
&& state != state_git_diff_seen)
{
SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line,
scratch_pool));
state = state_start;
}
}
while (! eof);
patch->reverse = reverse;
if (reverse)
{
const char *temp;
svn_tristate_t ts_tmp;
temp = patch->old_filename;
patch->old_filename = patch->new_filename;
patch->new_filename = temp;
switch (patch->operation)
{
case svn_diff_op_added:
patch->operation = svn_diff_op_deleted;
break;
case svn_diff_op_deleted:
patch->operation = svn_diff_op_added;
break;
case svn_diff_op_modified:
break;
case svn_diff_op_copied:
case svn_diff_op_moved:
break;
case svn_diff_op_unchanged:
break;
}
ts_tmp = patch->old_executable_bit;
patch->old_executable_bit = patch->new_executable_bit;
patch->new_executable_bit = ts_tmp;
ts_tmp = patch->old_symlink_bit;
patch->old_symlink_bit = patch->new_symlink_bit;
patch->new_symlink_bit = ts_tmp;
}
if (patch->old_filename == NULL || patch->new_filename == NULL)
{
patch = NULL;
}
else
{
if (state == state_binary_patch_found)
{
SVN_ERR(parse_binary_patch(patch, patch_file->apr_file, reverse,
result_pool, iterpool));
}
SVN_ERR(parse_hunks(patch, patch_file->apr_file, ignore_whitespace,
result_pool, iterpool));
}
svn_pool_destroy(iterpool);
SVN_ERR(svn_io_file_get_offset(&patch_file->next_patch_offset,
patch_file->apr_file, scratch_pool));
if (patch && patch->hunks)
{
svn_sort__array(patch->hunks, compare_hunks);
}
*patch_p = patch;
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff_close_patch_file(svn_patch_file_t *patch_file,
apr_pool_t *scratch_pool)
{
return svn_error_trace(svn_io_file_close(patch_file->apr_file,
scratch_pool));
}