diff --git a/src/apply.c b/src/apply.c index f1bd9f4b5..c667c6308 100644 --- a/src/apply.c +++ b/src/apply.c @@ -13,6 +13,8 @@ #include "diff_patch.h" #include "fileops.h" #include "apply.h" +#include "delta.h" +#include "zstream.h" #define apply_err(...) \ ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 ) @@ -239,6 +241,87 @@ done: return error; } +static int apply_binary_delta( + git_buf *out, + const char *source, + size_t source_len, + git_diff_binary_file *binary_file) +{ + git_buf inflated = GIT_BUF_INIT; + int error = 0; + + /* no diff means identical contents */ + if (binary_file->datalen == 0) + return git_buf_put(out, source, source_len); + + error = git_zstream_inflatebuf(&inflated, + binary_file->data, binary_file->datalen); + + if (!error && inflated.size != binary_file->inflatedlen) { + error = apply_err("inflated delta does not match expected length"); + git_buf_free(out); + } + + if (error < 0) + goto done; + + if (binary_file->type == GIT_DIFF_BINARY_DELTA) { + void *data; + size_t data_len; + + error = git_delta_apply(&data, &data_len, (void *)source, source_len, + (void *)inflated.ptr, inflated.size); + + out->ptr = data; + out->size = data_len; + out->asize = data_len; + } + else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) { + git_buf_swap(out, &inflated); + } + else { + error = apply_err("unknown binary delta type"); + goto done; + } + +done: + git_buf_free(&inflated); + return error; +} + +static int apply_binary( + git_buf *out, + const char *source, + size_t source_len, + git_patch *patch) +{ + git_buf reverse = GIT_BUF_INIT; + int error; + + /* first, apply the new_file delta to the given source */ + if ((error = apply_binary_delta(out, source, source_len, + &patch->binary.new_file)) < 0) + goto done; + + /* second, apply the old_file delta to sanity check the result */ + if ((error = apply_binary_delta(&reverse, out->ptr, out->size, + &patch->binary.old_file)) < 0) + goto done; + + if (source_len != reverse.size || + memcmp(source, reverse.ptr, source_len) != 0) { + error = apply_err("binary patch did not apply cleanly"); + goto done; + } + +done: + if (error < 0) + git_buf_free(out); + + git_buf_free(&reverse); + return error; +} + int git_apply__patch( git_buf *contents_out, char **filename_out, @@ -262,10 +345,14 @@ int git_apply__patch( patch->nfile.file->mode : GIT_FILEMODE_BLOB; } - /* If the patch is empty, simply keep the source unchanged */ - if (patch->hunks.size == 0) - git_buf_put(contents_out, source, source_len); - else if ((error = apply_hunks(contents_out, source, source_len, patch)) < 0) + if (patch->delta->flags & GIT_DIFF_FLAG_BINARY) + error = apply_binary(contents_out, source, source_len, patch); + else if (patch->hunks.size) + error = apply_hunks(contents_out, source, source_len, patch); + else + error = git_buf_put(contents_out, source, source_len); + + if (error) goto done; if (patch->delta->status == GIT_DELTA_DELETED && diff --git a/src/diff_print.c b/src/diff_print.c index dae9e341d..d18348462 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -415,6 +415,9 @@ static int diff_print_patch_file_binary( (error = diff_print_load_content(pi, delta)) < 0) return error; + if (binary->new_file.datalen == 0 && binary->old_file.datalen == 0) + return 0; + pre_binary_size = pi->buf->size; git_buf_printf(pi->buf, "GIT binary patch\n"); pi->line.num_lines++; diff --git a/tests/apply/apply_common.h b/tests/apply/apply_common.h index 595b96e31..f4cb2ff84 100644 --- a/tests/apply/apply_common.h +++ b/tests/apply/apply_common.h @@ -473,3 +473,102 @@ "+patch file\n" \ "-it's something else\n" \ " entirely!" + +/* binary contents */ + +#define FILE_BINARY_LITERAL_ORIGINAL "\x00\x00\x0a" +#define FILE_BINARY_LITERAL_ORIGINAL_LEN 3 + +#define FILE_BINARY_LITERAL_MODIFIED "\x00\x00\x01\x02\x0a" +#define FILE_BINARY_LITERAL_MODIFIED_LEN 5 + +#define PATCH_BINARY_LITERAL \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \ + "GIT binary patch\n" \ + "literal 5\n" \ + "Mc${NkU}WL~000&M4gdfE\n" \ + "\n" \ + "literal 3\n" \ + "Kc${Nk-~s>u4FC%O\n\n" + +#define FILE_BINARY_DELTA_ORIGINAL \ + "\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x54\x68\x69" \ + "\x73\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \ + "\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \ + "\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \ + "\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \ + "\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \ + "\x6f\x66\x20\x69\x74\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \ + "\x00\x01\x02\x0a\x53\x6f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \ + "\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \ + "\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \ + "\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \ + "\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a" +#define FILE_BINARY_DELTA_ORIGINAL_LEN 209 + +#define FILE_BINARY_DELTA_MODIFIED \ + "\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x5a\x5a\x5a" \ + "\x5a\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \ + "\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \ + "\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \ + "\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \ + "\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \ + "\x6f\x66\x20\x49\x54\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \ + "\x00\x01\x02\x0a\x53\x4f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \ + "\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \ + "\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \ + "\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \ + "\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a" +#define FILE_BINARY_DELTA_MODIFIED_LEN 209 + +#define PATCH_BINARY_DELTA \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \ + "GIT binary patch\n" \ + "delta 48\n" \ + "kc$~Y)c#%<%fq{_;hPk4EV4`4>uxE%K7m7r%|HL+L0In7XGynhq\n" \ + "\n" \ + "delta 48\n" \ + "mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n" + +#define PATCH_BINARY_ADD \ + "diff --git a/binary.bin b/binary.bin\n" \ + "new file mode 100644\n" \ + "index 0000000000000000000000000000000000000000..7c94f9e60bf366033d98e0d551ae37d30faef74a\n" \ + "GIT binary patch\n" \ + "literal 209\n" \ + "zc${60u?oUK5JXSQe8qG&;(u6KCC_\n" \ + "zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \ + "kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n" \ + "\n" \ + "literal 0\n" \ + "Hc$@C_\n" \ + "zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \ + "kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n\n" + +/* contains an old side that does not match the expected source */ +#define PATCH_BINARY_NOT_REVERSIBLE \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \ + "GIT binary patch\n" \ + "literal 5\n" \ + "Mc${NkU}WL~000&M4gdfE\n" \ + "\n" \ + "delta 48\n" \ + "mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n" diff --git a/tests/apply/fromdiff.c b/tests/apply/fromdiff.c index af0541de8..ae37d0719 100644 --- a/tests/apply/fromdiff.c +++ b/tests/apply/fromdiff.c @@ -8,10 +8,13 @@ #include "apply_common.h" static git_repository *repo = NULL; +static git_diff_options binary_opts = GIT_DIFF_OPTIONS_INIT; void test_apply_fromdiff__initialize(void) { repo = cl_git_sandbox_init("renames"); + + binary_opts.flags |= GIT_DIFF_SHOW_BINARY; } void test_apply_fromdiff__cleanup(void) @@ -19,10 +22,10 @@ void test_apply_fromdiff__cleanup(void) cl_git_sandbox_cleanup(); } -static int apply_buf( - const char *old, +static int apply_gitbuf( + const git_buf *old, const char *oldname, - const char *new, + const git_buf *new, const char *newname, const char *patch_expected, const git_diff_options *diff_opts) @@ -35,22 +38,27 @@ static int apply_buf( int error; cl_git_pass(git_patch_from_buffers(&patch, - old, old ? strlen(old) : 0, oldname, - new, new ? strlen(new) : 0, newname, + old ? old->ptr : NULL, old ? old->size : 0, + oldname, + new ? new->ptr : NULL, new ? new->size : 0, + newname, diff_opts)); - cl_git_pass(git_patch_to_buf(&patchbuf, patch)); - cl_assert_equal_s(patch_expected, patchbuf.ptr); + if (patch_expected) { + cl_git_pass(git_patch_to_buf(&patchbuf, patch)); + cl_assert_equal_s(patch_expected, patchbuf.ptr); + } - error = git_apply__patch(&result, &filename, &mode, old, old ? strlen(old) : 0, patch); + error = git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch); if (error == 0 && new == NULL) { cl_assert_equal_i(0, result.size); cl_assert_equal_p(NULL, filename); cl_assert_equal_i(0, mode); - } else { - cl_assert_equal_s(new, result.ptr); - cl_assert_equal_s("file.txt", filename); + } + else if (error == 0) { + cl_assert_equal_s(new->ptr, result.ptr); + cl_assert_equal_s(newname ? newname : oldname, filename); cl_assert_equal_i(0100644, mode); } @@ -62,6 +70,32 @@ static int apply_buf( return error; } +static int apply_buf( + const char *old, + const char *oldname, + const char *new, + const char *newname, + const char *patch_expected, + const git_diff_options *diff_opts) +{ + git_buf o = GIT_BUF_INIT, n = GIT_BUF_INIT, + *optr = NULL, *nptr = NULL; + + if (old) { + o.ptr = (char *)old; + o.size = strlen(old); + optr = &o; + } + + if (new) { + n.ptr = (char *)new; + n.size = strlen(new); + nptr = &n; + } + + return apply_gitbuf(optr, oldname, nptr, newname, patch_expected, diff_opts); +} + void test_apply_fromdiff__change_middle(void) { cl_git_pass(apply_buf( @@ -182,3 +216,74 @@ void test_apply_fromdiff__no_change(void) FILE_ORIGINAL, "file.txt", "", NULL)); } + +void test_apply_fromdiff__binary_add(void) +{ + git_buf newfile = GIT_BUF_INIT; + + newfile.ptr = FILE_BINARY_DELTA_MODIFIED; + newfile.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + NULL, NULL, + &newfile, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_no_change(void) +{ + git_buf original = GIT_BUF_INIT; + + original.ptr = FILE_BINARY_DELTA_ORIGINAL; + original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &original, "binary.bin", + "", &binary_opts)); +} + +void test_apply_fromdiff__binary_change_delta(void) +{ + git_buf original = GIT_BUF_INIT, modified = GIT_BUF_INIT; + + original.ptr = FILE_BINARY_DELTA_ORIGINAL; + original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; + + modified.ptr = FILE_BINARY_DELTA_MODIFIED; + modified.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &modified, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_change_literal(void) +{ + git_buf original = GIT_BUF_INIT, modified = GIT_BUF_INIT; + + original.ptr = FILE_BINARY_LITERAL_ORIGINAL; + original.size = FILE_BINARY_LITERAL_ORIGINAL_LEN; + + modified.ptr = FILE_BINARY_LITERAL_MODIFIED; + modified.size = FILE_BINARY_LITERAL_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &modified, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_delete(void) +{ + git_buf original = GIT_BUF_INIT; + + original.ptr = FILE_BINARY_DELTA_MODIFIED; + original.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + NULL, NULL, + NULL, &binary_opts)); +} diff --git a/tests/apply/fromfile.c b/tests/apply/fromfile.c index cbf50d82d..75b318523 100644 --- a/tests/apply/fromfile.c +++ b/tests/apply/fromfile.c @@ -299,3 +299,30 @@ void test_apply_fromfile__fail_not_a_patch(void) cl_git_fail(git_patch_from_patchfile(&patch, PATCH_NOT_A_PATCH, strlen(PATCH_NOT_A_PATCH))); } + +/* +void test_apply_fromdiff__binary_change_must_be_reversible(void) +{ + git_buf original = GIT_BUF_INIT, modified = GIT_BUF_INIT, + result = GIT_BUF_INIT; + char *filename; + unsigned int mode; + + original.ptr = FILE_BINARY_DELTA_ORIGINAL; + original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; + + modified.ptr = FILE_BINARY_DELTA_MODIFIED; + modified.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_fail(git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch); + + cl_git_fail(apply_gitbuf( + &original, "binary.bin", + &modified, "binary.bin", + PATCH_BINARY_NOT_REVERSIBLE, &binary_opts)); + cl_assert_equal_s("binary patch did not apply cleanly", giterr_last()->message); + + git_buf_free(&result); + git__free(filename); +} +*/