diff options
author | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 |
---|---|---|
committer | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 |
commit | 3faf8d6bffc5d0fb2525ba37bb504c53366caf9d (patch) | |
tree | 7e47911263e75034b767fe34b2f8d3d17e91f66d /subversion/libsvn_diff | |
parent | a55fb3c0d5eca7d887798125d5b95942b1f01d4b (diff) | |
download | src-3faf8d6bffc5d0fb2525ba37bb504c53366caf9d.tar.gz src-3faf8d6bffc5d0fb2525ba37bb504c53366caf9d.zip |
Import Subversion-1.10.0vendor/subversion/subversion-1.10.0
Notes
Notes:
svn path=/vendor/subversion/dist/; revision=333347
svn path=/vendor/subversion/subversion-1.10.0/; revision=333348; tag=vendor/subversion/subversion-1.10.0
Diffstat (limited to 'subversion/libsvn_diff')
-rw-r--r-- | subversion/libsvn_diff/binary_diff.c | 74 | ||||
-rw-r--r-- | subversion/libsvn_diff/diff.h | 10 | ||||
-rw-r--r-- | subversion/libsvn_diff/diff3.c | 27 | ||||
-rw-r--r-- | subversion/libsvn_diff/diff_file.c | 70 | ||||
-rw-r--r-- | subversion/libsvn_diff/diff_memory.c | 34 | ||||
-rw-r--r-- | subversion/libsvn_diff/parse-diff.c | 996 |
6 files changed, 1065 insertions, 146 deletions
diff --git a/subversion/libsvn_diff/binary_diff.c b/subversion/libsvn_diff/binary_diff.c index 035794dbed57..5ca37052e3a0 100644 --- a/subversion/libsvn_diff/binary_diff.c +++ b/subversion/libsvn_diff/binary_diff.c @@ -28,6 +28,10 @@ #include "svn_diff.h" #include "svn_types.h" +#include "diff.h" + +#include "svn_private_config.h" + /* Copies the data from ORIGINAL_STREAM to a temporary file, returning both the original and compressed size. */ static svn_error_t * @@ -42,7 +46,6 @@ create_compressed(apr_file_t **result, { svn_stream_t *compressed; svn_filesize_t bytes_read = 0; - apr_finfo_t finfo; apr_size_t rd; SVN_ERR(svn_io_open_uniquely_named(result, NULL, NULL, "diffgz", @@ -56,7 +59,7 @@ create_compressed(apr_file_t **result, if (original_stream) do { - char buffer[SVN_STREAM_CHUNK_SIZE]; + char buffer[SVN__STREAM_CHUNK_SIZE]; rd = sizeof(buffer); if (cancel_func) @@ -67,7 +70,7 @@ create_compressed(apr_file_t **result, bytes_read += rd; SVN_ERR(svn_stream_write(compressed, buffer, &rd)); } - while(rd == SVN_STREAM_CHUNK_SIZE); + while(rd == SVN__STREAM_CHUNK_SIZE); else { apr_size_t zero = 0; @@ -77,8 +80,7 @@ create_compressed(apr_file_t **result, SVN_ERR(svn_stream_close(compressed)); /* Flush compression */ *full_size = bytes_read; - SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, *result, scratch_pool)); - *compressed_size = finfo.size; + SVN_ERR(svn_io_file_size_get(compressed_size, *result, scratch_pool)); return SVN_NO_ERROR; } @@ -92,6 +94,66 @@ static const char b85str[] = "abcdefghijklmnopqrstuvwxyz" "!#$%&()*+-;<=>?@^_`{|}~"; +/* Helper function for svn_diff__base85_decode_line */ +static svn_error_t * +base85_value(int *value, char c) +{ + const char *p = strchr(b85str, c); + if (!p) + return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL, + _("Invalid base85 value")); + + /* It's safe to cast the ptrdiff_t value of the pointer difference + to int because the value will always be in the range [0..84]. */ + *value = (int)(p - b85str); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_diff__base85_decode_line(char *output_data, + apr_ssize_t output_len, + const char *base85_data, + apr_ssize_t base85_len, + apr_pool_t *scratch_pool) +{ + { + apr_ssize_t expected_data = (output_len + 3) / 4 * 5; + + if (base85_len != expected_data) + return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL, + _("Unexpected base85 line length")); + } + + while (base85_len) + { + unsigned info = 0; + apr_ssize_t i, n; + + for (i = 0; i < 5; i++) + { + int value; + + SVN_ERR(base85_value(&value, base85_data[i])); + info *= 85; + info += value; + } + + for (i = 0, n=24; i < 4; i++, n-=8) + { + if (i < output_len) + output_data[i] = (info >> n) & 0xFF; + } + + base85_data += 5; + base85_len -= 5; + output_data += 4; + output_len -= 4; + } + + return SVN_NO_ERROR; +} + + /* Git length encoding table for write_literal */ static const char b85lenstr[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -215,7 +277,5 @@ svn_diff_output_binary(svn_stream_t *output_stream, scratch_pool)); svn_pool_destroy(subpool); - SVN_ERR(svn_stream_puts(output_stream, APR_EOL_STR)); - return SVN_NO_ERROR; } diff --git a/subversion/libsvn_diff/diff.h b/subversion/libsvn_diff/diff.h index 51a84c640580..7628e65cc7b5 100644 --- a/subversion/libsvn_diff/diff.h +++ b/subversion/libsvn_diff/diff.h @@ -214,4 +214,14 @@ svn_diff__unified_write_hunk_header(svn_stream_t *output_stream, apr_pool_t *scratch_pool); +/* Decodes a single line of base85 data in BASE85_DATA of length BASE85_LEN, + to OUTPUT_DATA of length OUTPUT_LEN. + */ +svn_error_t * +svn_diff__base85_decode_line(char *output_data, + apr_ssize_t output_len, + const char *base85_data, + apr_ssize_t base85_len, + apr_pool_t *scratch_pool); + #endif /* DIFF_H */ diff --git a/subversion/libsvn_diff/diff3.c b/subversion/libsvn_diff/diff3.c index 8b7c9b332817..aa247468e5ff 100644 --- a/subversion/libsvn_diff/diff3.c +++ b/subversion/libsvn_diff/diff3.c @@ -29,6 +29,7 @@ #include "svn_pools.h" #include "svn_error.h" #include "svn_diff.h" +#include "svn_sorts.h" #include "svn_types.h" #include "diff.h" @@ -474,21 +475,23 @@ svn_diff_diff3_2(svn_diff_t **diff, - (original_sync - lcs_om->position[0]->offset); latest_length = lcs_ol->length - (original_sync - lcs_ol->position[0]->offset); - common_length = modified_length < latest_length - ? modified_length : latest_length; + common_length = MIN(modified_length, latest_length); - (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref)); + if (common_length > 0) + { + (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref)); - (*diff_ref)->type = svn_diff__type_common; - (*diff_ref)->original_start = original_sync - 1; - (*diff_ref)->original_length = common_length; - (*diff_ref)->modified_start = modified_sync - 1; - (*diff_ref)->modified_length = common_length; - (*diff_ref)->latest_start = latest_sync - 1; - (*diff_ref)->latest_length = common_length; - (*diff_ref)->resolved_diff = NULL; + (*diff_ref)->type = svn_diff__type_common; + (*diff_ref)->original_start = original_sync - 1; + (*diff_ref)->original_length = common_length; + (*diff_ref)->modified_start = modified_sync - 1; + (*diff_ref)->modified_length = common_length; + (*diff_ref)->latest_start = latest_sync - 1; + (*diff_ref)->latest_length = common_length; + (*diff_ref)->resolved_diff = NULL; - diff_ref = &(*diff_ref)->next; + diff_ref = &(*diff_ref)->next; + } /* Set the new offsets */ original_start = original_sync + common_length; diff --git a/subversion/libsvn_diff/diff_file.c b/subversion/libsvn_diff/diff_file.c index f54522e7c9c6..d0182c8b3555 100644 --- a/subversion/libsvn_diff/diff_file.c +++ b/subversion/libsvn_diff/diff_file.c @@ -777,7 +777,6 @@ datasources_open(void *baton, { svn_diff__file_baton_t *file_baton = baton; struct file_info files[4]; - apr_finfo_t finfo[4]; apr_off_t length[4]; #ifndef SVN_DISABLE_PREFIX_SUFFIX_SCANNING svn_boolean_t reached_one_eof; @@ -792,14 +791,14 @@ datasources_open(void *baton, /* Open datasources and read first chunk */ for (i = 0; i < datasources_len; i++) { + svn_filesize_t filesize; 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; + SVN_ERR(svn_io_file_size_get(&filesize, file->file, file_baton->pool)); + file->size = filesize; + length[i] = filesize > CHUNK_SIZE ? CHUNK_SIZE : filesize; file->buffer = apr_palloc(file_baton->pool, (apr_size_t) length[i]); SVN_ERR(read_chunk(file->file, file->buffer, length[i], 0, file_baton->pool)); @@ -1243,17 +1242,20 @@ svn_diff_file_options_parse(svn_diff_file_options_t *options, { apr_getopt_t *os; struct opt_parsing_error_baton_t opt_parsing_error_baton; - /* Make room for each option (starting at index 1) plus trailing NULL. */ - const char **argv = apr_palloc(pool, sizeof(char*) * (args->nelts + 2)); + apr_array_header_t *argv; opt_parsing_error_baton.err = NULL; opt_parsing_error_baton.pool = pool; - argv[0] = ""; - memcpy(argv + 1, args->elts, sizeof(char*) * args->nelts); - argv[args->nelts + 1] = NULL; + /* Make room for each option (starting at index 1) plus trailing NULL. */ + argv = apr_array_make(pool, args->nelts + 2, sizeof(char*)); + APR_ARRAY_PUSH(argv, const char *) = ""; + apr_array_cat(argv, args); + APR_ARRAY_PUSH(argv, const char *) = NULL; - apr_getopt_init(&os, pool, args->nelts + 1, argv); + apr_getopt_init(&os, pool, + argv->nelts - 1 /* Exclude trailing NULL */, + (const char *const *) argv->elts); /* Capture any error message from apr_getopt_long(). This will typically * say which option is wrong, which we would not otherwise know. */ @@ -1417,6 +1419,10 @@ typedef struct svn_diff__file_output_baton_t int context_size; + /* Cancel handler */ + svn_cancel_func_t cancel_func; + void *cancel_baton; + apr_pool_t *pool; } svn_diff__file_output_baton_t; @@ -1598,10 +1604,15 @@ static APR_INLINE svn_error_t * output_unified_diff_range(svn_diff__file_output_baton_t *output_baton, int source, svn_diff__file_output_unified_type_e type, - apr_off_t until) + apr_off_t until, + svn_cancel_func_t cancel_func, + void *cancel_baton) { while (output_baton->current_line[source] < until) { + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + SVN_ERR(output_unified_line(output_baton, type, source)); } return SVN_NO_ERROR; @@ -1627,7 +1638,8 @@ output_unified_flush_hunk(svn_diff__file_output_baton_t *baton) /* Add trailing context to the hunk */ SVN_ERR(output_unified_diff_range(baton, 0 /* original */, svn_diff__file_output_unified_context, - target_line)); + target_line, + baton->cancel_func, baton->cancel_baton)); old_start = baton->hunk_start[0]; new_start = baton->hunk_start[1]; @@ -1715,7 +1727,9 @@ output_unified_diff_modified(void *baton, /* Original: Output the context preceding the changed range */ SVN_ERR(output_unified_diff_range(output_baton, 0 /* original */, svn_diff__file_output_unified_context, - original_start)); + original_start, + output_baton->cancel_func, + output_baton->cancel_baton)); } } @@ -1723,7 +1737,9 @@ output_unified_diff_modified(void *baton, to display */ SVN_ERR(output_unified_diff_range(output_baton, 0 /* original */, svn_diff__file_output_unified_skip, - original_start - context_prefix_length)); + original_start - context_prefix_length, + output_baton->cancel_func, + output_baton->cancel_baton)); /* Note that the above skip stores data for the show_c_function support below */ @@ -1769,20 +1785,28 @@ output_unified_diff_modified(void *baton, /* Modified: Skip lines until we are at the start of the changed range */ SVN_ERR(output_unified_diff_range(output_baton, 1 /* modified */, svn_diff__file_output_unified_skip, - modified_start)); + modified_start, + output_baton->cancel_func, + output_baton->cancel_baton)); /* Original: Output the context preceding the changed range */ SVN_ERR(output_unified_diff_range(output_baton, 0 /* original */, svn_diff__file_output_unified_context, - original_start)); + original_start, + output_baton->cancel_func, + output_baton->cancel_baton)); /* Both: Output the changed range */ SVN_ERR(output_unified_diff_range(output_baton, 0 /* original */, svn_diff__file_output_unified_delete, - original_start + original_length)); + original_start + original_length, + output_baton->cancel_func, + output_baton->cancel_baton)); SVN_ERR(output_unified_diff_range(output_baton, 1 /* modified */, svn_diff__file_output_unified_insert, - modified_start + modified_length)); + modified_start + modified_length, + output_baton->cancel_func, + output_baton->cancel_baton)); return SVN_NO_ERROR; } @@ -1843,6 +1867,8 @@ svn_diff_file_output_unified4(svn_stream_t *output_stream, memset(&baton, 0, sizeof(baton)); baton.output_stream = output_stream; + baton.cancel_func = cancel_func; + baton.cancel_baton = cancel_baton; baton.pool = pool; baton.header_encoding = header_encoding; baton.path[0] = original_path; @@ -1956,7 +1982,7 @@ typedef struct context_saver_t { const char **data; /* const char *data[context_size] */ apr_size_t *len; /* apr_size_t len[context_size] */ apr_size_t next_slot; - apr_size_t total_written; + apr_ssize_t total_writes; } context_saver_t; @@ -1972,7 +1998,7 @@ context_saver_stream_write(void *baton, cs->data[cs->next_slot] = data; cs->len[cs->next_slot] = *len; cs->next_slot = (cs->next_slot + 1) % cs->context_size; - cs->total_written++; + cs->total_writes++; } return SVN_NO_ERROR; } @@ -2252,7 +2278,7 @@ output_conflict_with_context(svn_diff3__file_output_baton_t *btn, trailing context)? If so, flush it. */ if (btn->output_stream == btn->context_saver->stream) { - if (btn->context_saver->total_written > btn->context_size) + if (btn->context_saver->total_writes > btn->context_size) SVN_ERR(svn_stream_puts(btn->real_output_stream, "@@\n")); SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream)); } diff --git a/subversion/libsvn_diff/diff_memory.c b/subversion/libsvn_diff/diff_memory.c index d9d800d7162c..3a35e9d7e1e9 100644 --- a/subversion/libsvn_diff/diff_memory.c +++ b/subversion/libsvn_diff/diff_memory.c @@ -688,7 +688,7 @@ typedef struct context_saver_t { const char **data; /* const char *data[context_size] */ apr_size_t *len; /* apr_size_t len[context_size] */ apr_size_t next_slot; - apr_size_t total_written; + apr_ssize_t total_writes; } context_saver_t; @@ -701,7 +701,7 @@ context_saver_stream_write(void *baton, cs->data[cs->next_slot] = data; cs->len[cs->next_slot] = *len; cs->next_slot = (cs->next_slot + 1) % cs->context_size; - cs->total_written++; + cs->total_writes++; return SVN_NO_ERROR; } @@ -822,13 +822,11 @@ make_trailing_context_printer(merge_output_baton_t *btn) static svn_error_t * -output_merge_token_range(apr_size_t *lines_printed_p, - merge_output_baton_t *btn, +output_merge_token_range(merge_output_baton_t *btn, int idx, apr_off_t first, apr_off_t length) { apr_array_header_t *tokens = btn->sources[idx].tokens; - apr_size_t lines_printed = 0; for (; length > 0 && first < tokens->nelts; length--, first++) { @@ -838,12 +836,8 @@ output_merge_token_range(apr_size_t *lines_printed_p, /* Note that the trailing context printer assumes that svn_stream_write is called exactly once per line. */ SVN_ERR(svn_stream_write(btn->output_stream, token->data, &len)); - lines_printed++; } - if (lines_printed_p) - *lines_printed_p = lines_printed; - return SVN_NO_ERROR; } @@ -866,7 +860,7 @@ output_common_modified(void *baton, apr_off_t modified_start, apr_off_t modified_length, apr_off_t latest_start, apr_off_t latest_length) { - return output_merge_token_range(NULL, baton, 1/*modified*/, + return output_merge_token_range(baton, 1/*modified*/, modified_start, modified_length); } @@ -876,7 +870,7 @@ output_latest(void *baton, apr_off_t modified_start, apr_off_t modified_length, apr_off_t latest_start, apr_off_t latest_length) { - return output_merge_token_range(NULL, baton, 2/*latest*/, + return output_merge_token_range(baton, 2/*latest*/, latest_start, latest_length); } @@ -920,26 +914,26 @@ output_conflict(void *baton, style == svn_diff_conflict_display_modified_original_latest) { SVN_ERR(output_merge_marker(btn, 1/*modified*/)); - SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/, + SVN_ERR(output_merge_token_range(btn, 1/*modified*/, modified_start, modified_length)); if (style == svn_diff_conflict_display_modified_original_latest) { SVN_ERR(output_merge_marker(btn, 0/*original*/)); - SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/, + SVN_ERR(output_merge_token_range(btn, 0/*original*/, original_start, original_length)); } SVN_ERR(output_merge_marker(btn, 2/*separator*/)); - SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/, + SVN_ERR(output_merge_token_range(btn, 2/*latest*/, latest_start, latest_length)); SVN_ERR(output_merge_marker(btn, 3/*latest (end)*/)); } else if (style == svn_diff_conflict_display_modified) - SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/, + SVN_ERR(output_merge_token_range(btn, 1/*modified*/, modified_start, modified_length)); else if (style == svn_diff_conflict_display_latest) - SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/, + SVN_ERR(output_merge_token_range(btn, 2/*latest*/, latest_start, latest_length)); else /* unknown style */ SVN_ERR_MALFUNCTION(); @@ -983,7 +977,7 @@ output_conflict_with_context(void *baton, trailing context)? If so, flush it. */ if (btn->output_stream == btn->context_saver->stream) { - if (btn->context_saver->total_written > btn->context_size) + if (btn->context_saver->total_writes > btn->context_size) SVN_ERR(svn_stream_puts(btn->real_output_stream, "@@\n")); SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream)); } @@ -995,17 +989,17 @@ output_conflict_with_context(void *baton, SVN_ERR(output_conflict_with_context_marker(btn, btn->markers[1], modified_start, modified_length)); - SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/, + SVN_ERR(output_merge_token_range(btn, 1/*modified*/, modified_start, modified_length)); SVN_ERR(output_conflict_with_context_marker(btn, btn->markers[0], original_start, original_length)); - SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/, + SVN_ERR(output_merge_token_range(btn, 0/*original*/, original_start, original_length)); SVN_ERR(output_merge_marker(btn, 2/*separator*/)); - SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/, + SVN_ERR(output_merge_token_range(btn, 2/*latest*/, latest_start, latest_length)); SVN_ERR(output_conflict_with_context_marker(btn, btn->markers[3], latest_start, diff --git a/subversion/libsvn_diff/parse-diff.c b/subversion/libsvn_diff/parse-diff.c index 3f794b8b1fa7..f2159694c4f6 100644 --- a/subversion/libsvn_diff/parse-diff.c +++ b/subversion/libsvn_diff/parse-diff.c @@ -40,8 +40,13 @@ #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" + /* Helper macro for readability */ #define starts_with(str, start) \ (strncmp((str), (start), strlen(start)) == 0) @@ -59,7 +64,7 @@ struct svn_diff__hunk_range { struct svn_diff_hunk_t { /* The patch this hunk belongs to. */ - svn_patch_t *patch; + const svn_patch_t *patch; /* APR file handle to the patch file this hunk came from. */ apr_file_t *apr_file; @@ -80,8 +85,150 @@ struct svn_diff_hunk_t { /* Number of lines of leading and trailing hunk context. */ svn_linenum_t leading_context; svn_linenum_t trailing_context; + + /* Did we see a 'file does not end with eol' marker in this hunk? */ + svn_boolean_t original_no_final_eol; + svn_boolean_t modified_no_final_eol; + + /* Fuzz penalty, triggered by bad patch targets */ + svn_linenum_t original_fuzz; + svn_linenum_t modified_fuzz; }; +struct svn_diff_binary_patch_t { + /* The patch this hunk belongs to. */ + const svn_patch_t *patch; + + /* APR file handle to the patch file this hunk came from. */ + apr_file_t *apr_file; + + /* Offsets inside APR_FILE representing the location of the patch */ + apr_off_t src_start; + apr_off_t src_end; + svn_filesize_t src_filesize; /* Expanded/final size */ + + /* Offsets inside APR_FILE representing the location of the patch */ + apr_off_t dst_start; + apr_off_t dst_end; + svn_filesize_t dst_filesize; /* Expanded/final size */ +}; + +/* Common guts of svn_diff_hunk__create_adds_single_line() and + * svn_diff_hunk__create_deletes_single_line(). + * + * ADD is TRUE if adding and FALSE if deleting. + */ +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); /* The +1 is for the \n. */ + svn_stringbuf_t *buf = svn_stringbuf_create_ensure(end + 1, scratch_pool); + + hunk->patch = patch; + + /* hunk->apr_file is created below. */ + + hunk->diff_text_range.start = header_len; + hunk->diff_text_range.current = header_len; + + if (add) + { + hunk->original_text_range.start = 0; /* There's no "original" text. */ + 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 /* delete */ + { + 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; /* There's no "original" text. */ + 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; /* setting to '1' works too */ + } + + hunk->leading_context = 0; + hunk->trailing_context = 0; + + /* Create APR_FILE and put just a hunk in it (without a diff header). + * Save the offset of the last byte of the diff line. */ + 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 /* filename */, + NULL /* system tempdir */, + 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)); + /* No need to seek. */ + + *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) { @@ -142,6 +289,222 @@ 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; +} + +/* Baton for the base85 stream implementation */ +struct base85_baton_t +{ + apr_file_t *file; + apr_pool_t *iterpool; + char buffer[52]; /* Bytes on current line */ + apr_off_t next_pos; /* Start position of next line */ + apr_off_t end_pos; /* Position after last line */ + apr_size_t buf_size; /* Bytes available (52 unless at eof) */ + apr_size_t buf_pos; /* Bytes in linebuffer */ + svn_boolean_t done; /* At eof? */ +}; + +/* Implements svn_read_fn_t for the base85 read stream */ +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; /* *len = OK */ + } + + if (b85b->next_pos >= b85b->end_pos) + break; /* At EOF */ + 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; /* Handle as EOF */ + + 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; +} + +/* Implements svn_close_fn_t for the base85 read stream */ +static svn_error_t * +close_handler_base85(void *baton) +{ + struct base85_baton_t *b85b = baton; + + svn_pool_destroy(b85b->iterpool); + + return SVN_NO_ERROR; +} + +/* Gets a stream that reads decoded base85 data from a segment of a file. + The current implementation might assume that both start_pos and end_pos + are located at line boundaries. */ +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 /* only full read support */, + read_handler_base85); + svn_stream_set_close(base85s, close_handler_base85); + return base85s; +} + +/* Baton for the length verification stream functions */ +struct length_verify_baton_t +{ + svn_stream_t *inner; + svn_filesize_t remaining; +}; + +/* Implements svn_read_fn_t for the length verification stream */ +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; +} + +/* Implements svn_close_fn_t for the length verification stream */ +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)); +} + +/* Gets a stream that verifies on reads that the inner stream is exactly + of the specified length */ +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 /* only full read support */, + 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); + + /* ### If we (ever) want to support the DELTA format, then we should hook the + undelta handling here */ + + 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); + + /* ### If we (ever) want to support the DELTA format, then we should hook the + undelta handling here */ + + return get_verify_length_stream(s, bpatch->dst_filesize, result_pool); +} + /* Try to parse a positive number from a decimal number encoded * in the string NUMBER. Return parsed number in OFFSET, and return * TRUE if parsing was successful. */ @@ -279,7 +642,8 @@ parse_hunk_header(const char *header, svn_diff_hunk_t *hunk, * Leading unidiff symbols ('+', '-', and ' ') are removed from the line, * Any lines commencing with the VERBOTEN character are discarded. * VERBOTEN should be '+' or '-', depending on which form of hunk text - * is being read. + * is being read. NO_FINAL_EOL declares if the hunk contains a no final + * EOL marker. * * All other parameters are as in svn_diff_hunk_readline_original_text() * and svn_diff_hunk_readline_modified_text(). @@ -291,6 +655,7 @@ hunk_readline_original_or_modified(apr_file_t *file, 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) { @@ -298,27 +663,35 @@ hunk_readline_original_or_modified(apr_file_t *file, 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) { /* We're past the range. Indicate that no bytes can be read. */ *eof = TRUE; - if (eol) - *eol = NULL; + *eol = NULL; *stringbuf = svn_stringbuf_create_empty(result_pool); return SVN_NO_ERROR; } - pos = 0; - SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); + SVN_ERR(svn_io_file_get_offset(&pos, file, scratch_pool)); SVN_ERR(svn_io_file_seek(file, APR_SET, &range->current, scratch_pool)); + + /* It's not ITERPOOL because we use data allocated in LAST_POOL out + of the loop. */ + 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, - result_pool, scratch_pool)); - range->current = 0; - SVN_ERR(svn_io_file_seek(file, APR_CUR, &range->current, scratch_pool)); + 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); @@ -327,6 +700,7 @@ hunk_readline_original_or_modified(apr_file_t *file, { /* EOF, return an empty string. */ *stringbuf = svn_stringbuf_create_ensure(0, result_pool); + *eol = NULL; } else if (str->data[0] == '+' || str->data[0] == '-' || str->data[0] == ' ') { @@ -335,12 +709,37 @@ hunk_readline_original_or_modified(apr_file_t *file, } else { - /* Return the line as-is. */ + /* Return the line as-is. Handle as a chopped leading spaces */ *stringbuf = svn_stringbuf_dup(str, result_pool); } + if (!filtered && *eof && !*eol && *str->data) + { + /* Ok, we miss a final EOL in the patch file, but didn't see a + no eol marker line. + + We should report that we had an EOL or the patch code will + misbehave (and it knows nothing about no eol markers) */ + + 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)); + + /* Every patch file that has hunks has at least one EOL*/ + SVN_ERR_ASSERT(*eol != NULL); + } + + *eof = FALSE; + /* Fall through to seek back to the right location */ + } SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); + svn_pool_destroy(last_pool); return SVN_NO_ERROR; } @@ -359,6 +758,9 @@ svn_diff_hunk_readline_original_text(svn_diff_hunk_t *hunk, &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)); } @@ -377,6 +779,9 @@ svn_diff_hunk_readline_modified_text(svn_diff_hunk_t *hunk, &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)); } @@ -391,28 +796,60 @@ svn_diff_hunk_readline_diff_text(svn_diff_hunk_t *hunk, 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) { /* We're past the range. Indicate that no bytes can be read. */ *eof = TRUE; - if (eol) - *eol = NULL; + *eol = NULL; *stringbuf = svn_stringbuf_create_empty(result_pool); return SVN_NO_ERROR; } - pos = 0; - SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR, &pos, scratch_pool)); + 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)); - hunk->diff_text_range.current = 0; - SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR, - &hunk->diff_text_range.current, scratch_pool)); + SVN_ERR(svn_io_file_get_offset(&hunk->diff_text_range.current, + hunk->apr_file, scratch_pool)); + + if (*eof && !*eol && *line->data) + { + /* Ok, we miss a final EOL in the patch file, but didn't see a + no eol marker line. + + We should report that we had an EOL or the patch code will + misbehave (and it knows nothing about no eol markers) */ + + if (eol != &eol_p) + { + /* Lets pick the first eol we find in our patch file */ + 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)); + + /* Every patch file that has hunks has at least one EOL*/ + SVN_ERR_ASSERT(*eol != NULL); + } + + *eof = FALSE; + + /* Fall through to seek back to the right location */ + } + SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &pos, scratch_pool)); if (hunk->patch->reverse) @@ -619,6 +1056,8 @@ parse_next_hunk(svn_diff_hunk_t **hunk, 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; @@ -654,9 +1093,8 @@ parse_next_hunk(svn_diff_hunk_t **hunk, modified_end = 0; *hunk = apr_pcalloc(result_pool, sizeof(**hunk)); - /* Get current seek position -- APR has no ftell() :( */ - pos = 0; - SVN_ERR(svn_io_file_seek(apr_file, APR_CUR, &pos, scratch_pool)); + /* Get current seek position. */ + SVN_ERR(svn_io_file_get_offset(&pos, apr_file, scratch_pool)); /* Start out assuming noise. */ last_line_type = noise_line; @@ -673,8 +1111,7 @@ parse_next_hunk(svn_diff_hunk_t **hunk, iterpool, iterpool)); /* Update line offset for next iteration. */ - pos = 0; - SVN_ERR(svn_io_file_seek(apr_file, APR_CUR, &pos, iterpool)); + SVN_ERR(svn_io_file_get_offset(&pos, apr_file, iterpool)); /* Lines starting with a backslash indicate a missing EOL: * "\ No newline at end of file" or "end of property". */ @@ -715,6 +1152,11 @@ parse_next_hunk(svn_diff_hunk_t **hunk, } SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &pos, iterpool)); + /* Set for the type and context by using != the other type */ + if (last_line_type != modified_line) + original_no_final_eol = TRUE; + if (last_line_type != original_line) + modified_no_final_eol = TRUE; } continue; @@ -728,7 +1170,13 @@ parse_next_hunk(svn_diff_hunk_t **hunk, SVN_ERR(parse_mergeinfo(&found_mergeinfo, line, *hunk, patch, result_pool, iterpool)); if (found_mergeinfo) - continue; /* Proceed to the next line in the patch. */ + continue; /* Proceed to the next line in the svn:mergeinfo hunk. */ + else + { + /* Perhaps we can also use original_lines/modified_lines here */ + + in_hunk = FALSE; /* On to next property */ + } } if (in_hunk) @@ -745,24 +1193,38 @@ parse_next_hunk(svn_diff_hunk_t **hunk, } c = line->data[0]; - if (original_lines > 0 && modified_lines > 0 && - ((c == ' ') + if (c == ' ' + || ((original_lines > 0 && modified_lines > 0) + && ( /* Tolerate chopped leading spaces on empty lines. */ - || (! eof && line->len == 0) + (! eof && line->len == 0) /* Maybe tolerate chopped leading spaces on non-empty lines. */ - || (ignore_whitespace && c != del && c != add))) + || (ignore_whitespace && c != del && c != add)))) { /* It's a "context" line in the hunk. */ hunk_seen = TRUE; - original_lines--; - modified_lines--; + 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 (original_lines > 0 && c == del) + else if (c == del + && (original_lines > 0 || line->data[1] != del)) { /* It's a "deleted" line in the hunk. */ hunk_seen = TRUE; @@ -773,10 +1235,17 @@ parse_next_hunk(svn_diff_hunk_t **hunk, if (trailing_context > 0) trailing_context = 0; - original_lines--; + if (original_lines > 0) + original_lines--; + else + { + (*hunk)->original_length++; + (*hunk)->original_fuzz++; + } last_line_type = original_line; } - else if (modified_lines > 0 && c == add) + else if (c == add + && (modified_lines > 0 || line->data[1] != add)) { /* It's an "added" line in the hunk. */ hunk_seen = TRUE; @@ -787,7 +1256,13 @@ parse_next_hunk(svn_diff_hunk_t **hunk, if (trailing_context > 0) trailing_context = 0; - modified_lines--; + if (modified_lines > 0) + modified_lines--; + else + { + (*hunk)->modified_length++; + (*hunk)->modified_fuzz++; + } last_line_type = modified_line; } else @@ -803,7 +1278,6 @@ parse_next_hunk(svn_diff_hunk_t **hunk, * after the hunk text. */ end = last_line; } - if (original_end == 0) original_end = end; if (modified_end == 0) @@ -843,14 +1317,16 @@ parse_next_hunk(svn_diff_hunk_t **hunk, SVN_ERR(parse_prop_name(prop_name, line->data, "Added: ", result_pool)); if (*prop_name) - *prop_operation = svn_diff_op_added; + *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 = svn_diff_op_deleted; + *prop_operation = (patch->reverse ? svn_diff_op_added + : svn_diff_op_deleted); } else if (starts_with(line->data, "Modified: ")) { @@ -878,6 +1354,21 @@ parse_next_hunk(svn_diff_hunk_t **hunk, if (hunk_seen && start < end) { + /* Did we get the number of context lines announced in the header? + + If not... let's limit the number from the header to what we + actually have, and apply a fuzz penalty */ + 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; @@ -891,6 +1382,8 @@ parse_next_hunk(svn_diff_hunk_t **hunk, (*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 /* Something went wrong, just discard the result. */ @@ -917,16 +1410,19 @@ compare_hunks(const void *a, const void *b) /* Possible states of the diff header parser. */ enum parse_state { - state_start, /* initial */ - state_git_diff_seen, /* diff --git */ - state_git_tree_seen, /* a tree operation, rather then content change */ - state_git_minus_seen, /* --- /dev/null; or --- a/ */ - state_git_plus_seen, /* +++ /dev/null; or +++ a/ */ - state_move_from_seen, /* rename from foo.c */ - state_copy_from_seen, /* copy from foo.c */ - state_minus_seen, /* --- foo.c */ - state_unidiff_found, /* valid start of a regular unidiff header */ - state_git_header_found /* valid start of a --git diff header */ + state_start, /* initial */ + state_git_diff_seen, /* diff --git */ + state_git_tree_seen, /* a tree operation, rather than content change */ + state_git_minus_seen, /* --- /dev/null; or --- a/ */ + state_git_plus_seen, /* +++ /dev/null; or +++ a/ */ + state_old_mode_seen, /* old mode 100644 */ + state_git_mode_seen, /* new mode 100644 */ + state_move_from_seen, /* rename from foo.c */ + state_copy_from_seen, /* copy from foo.c */ + state_minus_seen, /* --- foo.c */ + state_unidiff_found, /* valid start of a regular unidiff header */ + state_git_header_found, /* valid start of a --git diff header */ + state_binary_patch_found /* valid start of binary patch */ }; /* Data type describing a valid state transition of the parser. */ @@ -1153,6 +1649,139 @@ git_plus(enum parse_state *new_state, char *line, svn_patch_t *patch, return SVN_NO_ERROR; } +/* Helper for git_old_mode() and git_new_mode(). Translate the git + * file mode MODE_STR into a binary "executable?" and "symlink?" state. */ +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 /* min */, + 0777777 /* max: six octal digits */, + 010 /* radix (octal) */)); + + /* Note: 0644 and 0755 are the only modes that can occur for plain files. + * We deliberately choose to parse only those values: we are strict in what + * we accept _and_ in what we produce. + * + * (Having said that, though, we could consider relaxing the parser to also + * map + * (mode & 0111) == 0000 -> svn_tristate_false + * (mode & 0111) == 0111 -> svn_tristate_true + * [anything else] -> svn_tristate_unknown + * .) + */ + + switch (mode & 0777) + { + case 0644: + *executable_p = svn_tristate_false; + break; + + case 0755: + *executable_p = svn_tristate_true; + break; + + default: + /* Ignore unknown values. */ + *executable_p = svn_tristate_unknown; + break; + } + + switch (mode & 0170000 /* S_IFMT */) + { + case 0120000: /* S_IFLNK */ + *symlink_p = svn_tristate_true; + break; + + case 0100000: /* S_IFREG */ + case 0040000: /* S_IFDIR */ + *symlink_p = svn_tristate_false; + break; + + default: + /* Ignore unknown values. + (Including those generated by Subversion <= 1.9) */ + *symlink_p = svn_tristate_unknown; + break; + } + + return SVN_NO_ERROR; +} + +/* Parse the 'old mode ' line of a git extended unidiff. */ +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 + /* If this assert trips, the "old mode" is neither ...644 nor ...755 . */ + SVN_ERR_ASSERT(patch->old_executable_bit != svn_tristate_unknown); +#endif + + *new_state = state_old_mode_seen; + return SVN_NO_ERROR; +} + +/* Parse the 'new mode ' line of a git extended unidiff. */ +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 + /* If this assert trips, the "old mode" is neither ...644 nor ...755 . */ + SVN_ERR_ASSERT(patch->new_executable_bit != svn_tristate_unknown); +#endif + + /* Don't touch patch->operation. */ + + *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) +{ + /* We either have something like "index 33e5b38..0000000" (which we just + ignore as we are not interested in git specific shas) or something like + "index 33e5b38..0000000 120000" which tells us the mode, that isn't + changed by applying this patch. + + If the mode would have changed then we would see 'old mode' and 'new mode' + lines. + */ + 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)); + + /* There is no change.. so set the old values to the new values */ + patch->old_executable_bit = patch->new_executable_bit; + patch->old_symlink_bit = patch->new_symlink_bit; + } + + /* This function doesn't change the state! */ + /* *new_state = *new_state */ + return SVN_NO_ERROR; +} + /* Parse the 'rename from ' line of a git extended unidiff. */ static svn_error_t * git_move_from(enum parse_state *new_state, char *line, svn_patch_t *patch, @@ -1213,6 +1842,10 @@ 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; /* Filename already retrieved from diff --git header. */ @@ -1226,6 +1859,10 @@ 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; /* Filename already retrieved from diff --git header. */ @@ -1234,6 +1871,16 @@ git_deleted_file(enum parse_state *new_state, char *line, svn_patch_t *patch, return SVN_NO_ERROR; } +/* Parse the 'GIT binary patch' header */ +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; +} + + /* Add a HUNK associated with the property PROP_NAME to PATCH. */ static svn_error_t * add_property_hunk(svn_patch_t *patch, const char *prop_name, @@ -1346,24 +1993,163 @@ parse_hunks(svn_patch_t *patch, apr_file_t *apr_file, 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)); + + /* Update line offset for next iteration. */ + SVN_ERR(svn_io_file_get_offset(&pos, apr_file, iterpool)); + + if (in_blob) + { + char c = line->data[0]; + + /* 66 = len byte + (52/4*5) chars */ + if (((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + && line->len <= 66 + && !strchr(line->data, ':') + && !strchr(line->data, ' ')) + { + /* One more blop line */ + 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; /* Bad patch */ + } + else if (in_src) + { + patch->binary_patch = bpatch; /* SUCCESS! */ + 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; /* We don't support GIT deltas (yet) */ + } + svn_pool_destroy(iterpool); + + if (!eof) + /* Rewind to the start of the line just read, so subsequent calls + * don't end up skipping the line. It may contain a patch or hunk header.*/ + 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; /* SUCCESS */ + } + + /* Reverse patch if requested */ + 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; +} + /* State machine for the diff header parser. * Expected Input Required state Function to call */ 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_tree_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}, - {"rename from ", state_git_diff_seen, git_move_from}, - {"rename to ", state_move_from_seen, git_move_to}, - {"copy from ", state_git_diff_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}, + {"--- ", 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 * @@ -1389,6 +2175,10 @@ svn_diff_parse_next_patch(svn_patch_t **patch_p, } 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)); @@ -1410,9 +2200,8 @@ svn_diff_parse_next_patch(svn_patch_t **patch_p, if (! eof) { /* Update line offset for next iteration. */ - pos = 0; - SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_CUR, &pos, - iterpool)); + SVN_ERR(svn_io_file_get_offset(&pos, patch_file->apr_file, + iterpool)); } /* Run the state machine. */ @@ -1428,32 +2217,32 @@ svn_diff_parse_next_patch(svn_patch_t **patch_p, } } - if (state == state_unidiff_found || state == state_git_header_found) + if (state == state_unidiff_found + || state == state_git_header_found + || state == state_binary_patch_found) { /* We have a valid diff header, yay! */ break; } - else if (state == state_git_tree_seen && line_after_tree_header_read) + else if ((state == state_git_tree_seen || state == state_git_mode_seen) + && line_after_tree_header_read + && !valid_header_line) { - /* git patches can contain an index line after the file mode line */ - if (!starts_with(line->data, "index ")) - { - /* We have a valid diff header for a patch with only tree changes. - * Rewind to the start of the line just read, so subsequent calls - * to this function don't end up skipping the line -- it may - * contain a patch. */ - SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line, - scratch_pool)); - break; - } + /* We have a valid diff header for a patch with only tree changes. + * Rewind to the start of the line just read, so subsequent calls + * to this function don't end up skipping the line -- it may + * contain a patch. */ + SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line, + scratch_pool)); + break; } - else if (state == state_git_tree_seen) + 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 - && !starts_with(line->data, "index ")) + && state != state_git_diff_seen) { /* We've encountered an invalid diff header. * @@ -1471,9 +2260,38 @@ svn_diff_parse_next_patch(svn_patch_t **patch_p, 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; /* Stays modified. */ + + case svn_diff_op_copied: + case svn_diff_op_moved: + break; /* Stays copied or moved, just in the other direction. */ + case svn_diff_op_unchanged: + break; /* Stays unchanged, of course. */ + } + + 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) @@ -1482,16 +2300,24 @@ svn_diff_parse_next_patch(svn_patch_t **patch_p, patch = NULL; } else - SVN_ERR(parse_hunks(patch, patch_file->apr_file, ignore_whitespace, - result_pool, iterpool)); + { + if (state == state_binary_patch_found) + { + SVN_ERR(parse_binary_patch(patch, patch_file->apr_file, reverse, + result_pool, iterpool)); + /* And fall through in property parsing */ + } + + SVN_ERR(parse_hunks(patch, patch_file->apr_file, ignore_whitespace, + result_pool, iterpool)); + } svn_pool_destroy(iterpool); - patch_file->next_patch_offset = 0; - SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_CUR, - &patch_file->next_patch_offset, scratch_pool)); + SVN_ERR(svn_io_file_get_offset(&patch_file->next_patch_offset, + patch_file->apr_file, scratch_pool)); - if (patch) + if (patch && patch->hunks) { /* Usually, hunks appear in the patch sorted by their original line * offset. But just in case they weren't parsed in this order for |