| #include "clar_libgit2.h" |
| #include "posix.h" |
| #include "blob.h" |
| #include "filter.h" |
| #include "buf_text.h" |
| #include "git2/sys/filter.h" |
| #include "git2/sys/repository.h" |
| |
| static git_repository *g_repo = NULL; |
| |
| static git_filter *create_compress_filter(void); |
| static git_filter *compress_filter; |
| |
| void test_filter_stream__initialize(void) |
| { |
| compress_filter = create_compress_filter(); |
| |
| cl_git_pass(git_filter_register("compress", compress_filter, 50)); |
| g_repo = cl_git_sandbox_init("empty_standard_repo"); |
| } |
| |
| void test_filter_stream__cleanup(void) |
| { |
| cl_git_sandbox_cleanup(); |
| g_repo = NULL; |
| |
| git_filter_unregister("compress"); |
| git__free(compress_filter); |
| } |
| |
| #define CHUNKSIZE 10240 |
| |
| struct compress_stream { |
| git_writestream parent; |
| git_writestream *next; |
| git_filter_mode_t mode; |
| char current; |
| size_t current_chunk; |
| }; |
| |
| static int compress_stream_write__deflated(struct compress_stream *stream, const char *buffer, size_t len) |
| { |
| size_t idx = 0; |
| |
| while (len > 0) { |
| size_t chunkremain, chunksize; |
| |
| if (stream->current_chunk == 0) |
| stream->current = buffer[idx]; |
| |
| chunkremain = CHUNKSIZE - stream->current_chunk; |
| chunksize = min(chunkremain, len); |
| |
| stream->current_chunk += chunksize; |
| len -= chunksize; |
| idx += chunksize; |
| |
| if (stream->current_chunk == CHUNKSIZE) { |
| cl_git_pass(stream->next->write(stream->next, &stream->current, 1)); |
| stream->current_chunk = 0; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int compress_stream_write__inflated(struct compress_stream *stream, const char *buffer, size_t len) |
| { |
| char inflated[CHUNKSIZE]; |
| size_t i, j; |
| |
| for (i = 0; i < len; i++) { |
| for (j = 0; j < CHUNKSIZE; j++) |
| inflated[j] = buffer[i]; |
| |
| cl_git_pass(stream->next->write(stream->next, inflated, CHUNKSIZE)); |
| } |
| |
| return 0; |
| } |
| |
| static int compress_stream_write(git_writestream *s, const char *buffer, size_t len) |
| { |
| struct compress_stream *stream = (struct compress_stream *)s; |
| |
| return (stream->mode == GIT_FILTER_TO_ODB) ? |
| compress_stream_write__deflated(stream, buffer, len) : |
| compress_stream_write__inflated(stream, buffer, len); |
| } |
| |
| static int compress_stream_close(git_writestream *s) |
| { |
| struct compress_stream *stream = (struct compress_stream *)s; |
| cl_assert_equal_i(0, stream->current_chunk); |
| stream->next->close(stream->next); |
| return 0; |
| } |
| |
| static void compress_stream_free(git_writestream *stream) |
| { |
| git__free(stream); |
| } |
| |
| static int compress_filter_stream_init( |
| git_writestream **out, |
| git_filter *self, |
| void **payload, |
| const git_filter_source *src, |
| git_writestream *next) |
| { |
| struct compress_stream *stream = git__calloc(1, sizeof(struct compress_stream)); |
| cl_assert(stream); |
| |
| GIT_UNUSED(self); |
| GIT_UNUSED(payload); |
| |
| stream->parent.write = compress_stream_write; |
| stream->parent.close = compress_stream_close; |
| stream->parent.free = compress_stream_free; |
| stream->next = next; |
| stream->mode = git_filter_source_mode(src); |
| |
| *out = (git_writestream *)stream; |
| return 0; |
| } |
| |
| git_filter *create_compress_filter(void) |
| { |
| git_filter *filter = git__calloc(1, sizeof(git_filter)); |
| cl_assert(filter); |
| |
| filter->version = GIT_FILTER_VERSION; |
| filter->attributes = "+compress"; |
| filter->stream = compress_filter_stream_init; |
| |
| return filter; |
| } |
| |
| static void writefile(const char *filename, size_t numchunks) |
| { |
| git_buf path = GIT_BUF_INIT; |
| char buf[CHUNKSIZE]; |
| size_t i = 0, j = 0; |
| int fd; |
| |
| cl_git_pass(git_buf_joinpath(&path, "empty_standard_repo", filename)); |
| |
| fd = p_open(path.ptr, O_RDWR|O_CREAT, 0666); |
| cl_assert(fd >= 0); |
| |
| for (i = 0; i < numchunks; i++) { |
| for (j = 0; j < CHUNKSIZE; j++) { |
| buf[j] = i % 256; |
| } |
| |
| cl_git_pass(p_write(fd, buf, CHUNKSIZE)); |
| } |
| p_close(fd); |
| |
| git_buf_free(&path); |
| } |
| |
| static void test_stream(size_t numchunks) |
| { |
| git_index *index; |
| const git_index_entry *entry; |
| git_blob *blob; |
| struct stat st; |
| git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; |
| |
| checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; |
| |
| cl_git_mkfile( |
| "empty_standard_repo/.gitattributes", |
| "* compress\n"); |
| |
| /* write a file to disk */ |
| writefile("streamed_file", numchunks); |
| |
| /* place it in the index */ |
| cl_git_pass(git_repository_index(&index, g_repo)); |
| cl_git_pass(git_index_add_bypath(index, "streamed_file")); |
| cl_git_pass(git_index_write(index)); |
| |
| /* ensure it was appropriately compressed */ |
| cl_assert(entry = git_index_get_bypath(index, "streamed_file", 0)); |
| |
| cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); |
| cl_assert_equal_i(numchunks, git_blob_rawsize(blob)); |
| |
| /* check the file back out */ |
| cl_must_pass(p_unlink("empty_standard_repo/streamed_file")); |
| cl_git_pass(git_checkout_index(g_repo, index, &checkout_opts)); |
| |
| /* ensure it was decompressed */ |
| cl_must_pass(p_stat("empty_standard_repo/streamed_file", &st)); |
| cl_assert_equal_sz((numchunks * CHUNKSIZE), st.st_size); |
| |
| git_index_free(index); |
| git_blob_free(blob); |
| } |
| |
| /* write a 50KB file through the "compression" stream */ |
| void test_filter_stream__smallfile(void) |
| { |
| test_stream(5); |
| } |
| |
| /* optionally write a 500 MB file through the compression stream */ |
| void test_filter_stream__bigfile(void) |
| { |
| if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) |
| cl_skip(); |
| |
| test_stream(51200); |
| } |