diff --git a/include/git2/buffer.h b/include/git2/buffer.h index ae8681f13..36a61e6c9 100644 --- a/include/git2/buffer.h +++ b/include/git2/buffer.h @@ -54,6 +54,11 @@ typedef struct { size_t asize, size; } git_buf; +/** + * Static initializer for git_buf from static buffer + */ +#define GIT_BUF_INIT_CONST(STR,LEN) { (char *)(STR), 0, (size_t)(LEN) } + /** * Free the memory referred to by the git_buf. * diff --git a/include/git2/sys/filter.h b/include/git2/sys/filter.h index 36e97fe91..9a6720a3e 100644 --- a/include/git2/sys/filter.h +++ b/include/git2/sys/filter.h @@ -59,6 +59,19 @@ GIT_EXTERN(int) git_filter_list_new( GIT_EXTERN(int) git_filter_list_push( git_filter_list *fl, git_filter *filter, void *payload); +/** + * Look up how many filters are in the list + * + * We will attempt to apply all of these filters to any data passed in, + * but note that the filter apply action still has the option of skipping + * data that is passed in (for example, the CRLF filter will skip data + * that appears to be binary). + * + * @param fl A filter list + * @return The number of filters in the list + */ +GIT_EXTERN(size_t) git_filter_list_length(const git_filter_list *fl); + /** * A filter source represents a file/blob to be processed */ diff --git a/src/filter.c b/src/filter.c index 08467a631..0375b8b0e 100644 --- a/src/filter.c +++ b/src/filter.c @@ -548,6 +548,11 @@ int git_filter_list_push( return 0; } +size_t git_filter_list_length(const git_filter_list *fl) +{ + return fl ? git_array_size(fl->filters) : 0; +} + static int filter_list_out_buffer_from_raw( git_buf *out, const void *ptr, size_t size) { diff --git a/tests-clar/filter/crlf.c b/tests-clar/filter/crlf.c index ccd7ef450..ece2e6e5e 100644 --- a/tests-clar/filter/crlf.c +++ b/tests-clar/filter/crlf.c @@ -5,10 +5,16 @@ static git_repository *g_repo = NULL; void test_filter_crlf__initialize(void) { + git_config *cfg; + g_repo = cl_git_sandbox_init("crlf"); cl_git_mkfile("crlf/.gitattributes", "*.txt text\n*.bin binary\n*.crlf text eol=crlf\n*.lf text eol=lf\n"); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_string(cfg, "core.autocrlf", "true")); + git_config_free(cfg); } void test_filter_crlf__cleanup(void) @@ -22,13 +28,6 @@ void test_filter_crlf__to_worktree(void) git_filter *crlf; git_buf in = { 0 }, out = { 0 }; - { - git_config *cfg; - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_string(cfg, "core.autocrlf", "true")); - git_config_free(cfg); - } - cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_WORKTREE)); crlf = git_filter_lookup(GIT_FILTER_CRLF); @@ -57,13 +56,6 @@ void test_filter_crlf__to_odb(void) git_filter *crlf; git_buf in = { 0 }, out = { 0 }; - { - git_config *cfg; - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_string(cfg, "core.autocrlf", "true")); - git_config_free(cfg); - } - cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_ODB)); crlf = git_filter_lookup(GIT_FILTER_CRLF); diff --git a/tests-clar/filter/crlf.h b/tests-clar/filter/crlf.h index 8fadee950..9cb98ad4c 100644 --- a/tests-clar/filter/crlf.h +++ b/tests-clar/filter/crlf.h @@ -22,5 +22,4 @@ #define MORE_CRLF_TEXT_AS_LF "crlf\ncrlf\nlf\ncrlf\ncrlf\n" #define MORE_LF_TEXT_AS_LF "lf\nlf\ncrlf\nlf\nlf\n" - #endif diff --git a/tests-clar/filter/custom.c b/tests-clar/filter/custom.c new file mode 100644 index 000000000..4a2ff9fc4 --- /dev/null +++ b/tests-clar/filter/custom.c @@ -0,0 +1,247 @@ +#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" + +#define BITFLIP_FILTER_PRIORITY 20 +#define REVERSE_FILTER_PRIORITY 25 + +#define VERY_SECURE_ENCRYPTION(b) ((b) ^ 0xff) + +#ifdef GIT_WIN32 +# define NEWLINE "\r\n" +#else +# define NEWLINE "\n" +#endif + +static char workdir_data[] = + "some simple" NEWLINE + "data" NEWLINE + "that will be" NEWLINE + "trivially" NEWLINE + "scrambled." NEWLINE; + +/* Represents the data above scrambled (bits flipped) after \r\n -> \n + * conversion, then bytewise reversed + */ +static unsigned char bitflipped_and_reversed_data[] = + { 0xf5, 0xd1, 0x9b, 0x9a, 0x93, 0x9d, 0x92, 0x9e, 0x8d, 0x9c, 0x8c, + 0xf5, 0x86, 0x93, 0x93, 0x9e, 0x96, 0x89, 0x96, 0x8d, 0x8b, 0xf5, + 0x9a, 0x9d, 0xdf, 0x93, 0x93, 0x96, 0x88, 0xdf, 0x8b, 0x9e, 0x97, + 0x8b, 0xf5, 0x9e, 0x8b, 0x9e, 0x9b, 0xf5, 0x9a, 0x93, 0x8f, 0x92, + 0x96, 0x8c, 0xdf, 0x9a, 0x92, 0x90, 0x8c }; + +#define BITFLIPPED_AND_REVERSED_DATA_LEN 51 + +static git_repository *g_repo = NULL; + +static void register_custom_filters(void); + +void test_filter_custom__initialize(void) +{ + register_custom_filters(); + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile( + "empty_standard_repo/.gitattributes", + "hero* bitflip reverse\n" + "herofile text\n" + "heroflip -reverse\n"); +} + +void test_filter_custom__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static int bitflip_filter_apply( + git_filter *self, + void **payload, + git_buf *to, + const git_buf *from, + const git_filter_source *source) +{ + const unsigned char *src = (const unsigned char *)from->ptr; + unsigned char *dst; + size_t i; + + GIT_UNUSED(self); GIT_UNUSED(payload); + + /* verify that attribute path match worked as expected */ + cl_assert_equal_i( + 0, git__strncmp("hero", git_filter_source_path(source), 4)); + + if (!from->size) + return 0; + + cl_git_pass(git_buf_grow(to, from->size)); + + dst = (unsigned char *)to->ptr; + + for (i = 0; i < from->size; i++) + dst[i] = VERY_SECURE_ENCRYPTION(src[i]); + + to->size = from->size; + + return 0; +} + +static void bitflip_filter_free(git_filter *f) +{ + git__free(f); +} + +static git_filter *create_bitflip_filter(void) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = "+bitflip"; + filter->shutdown = bitflip_filter_free; + filter->apply = bitflip_filter_apply; + + return filter; +} + + +static int reverse_filter_apply( + git_filter *self, + void **payload, + git_buf *to, + const git_buf *from, + const git_filter_source *source) +{ + const unsigned char *src = (const unsigned char *)from->ptr; + const unsigned char *end = src + from->size; + unsigned char *dst; + + GIT_UNUSED(self); GIT_UNUSED(payload); GIT_UNUSED(source); + + /* verify that attribute path match worked as expected */ + cl_assert_equal_i( + 0, git__strncmp("hero", git_filter_source_path(source), 4)); + + if (!from->size) + return 0; + + cl_git_pass(git_buf_grow(to, from->size)); + + dst = (unsigned char *)to->ptr + from->size - 1; + + while (src < end) + *dst-- = *src++; + + to->size = from->size; + + return 0; +} + +static void reverse_filter_free(git_filter *f) +{ + git__free(f); +} + +static git_filter *create_reverse_filter(void) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = "+reverse"; + filter->shutdown = reverse_filter_free; + filter->apply = reverse_filter_apply; + + return filter; +} + +static void register_custom_filters(void) +{ + static int filters_registered = 0; + + if (!filters_registered) { + cl_git_pass(git_filter_register( + "bitflip", create_bitflip_filter(), BITFLIP_FILTER_PRIORITY)); + + cl_git_pass(git_filter_register( + "reverse", create_reverse_filter(), REVERSE_FILTER_PRIORITY)); + + filters_registered = 1; + } +} + + +void test_filter_custom__to_odb(void) +{ + git_filter_list *fl; + git_buf out = { 0 }; + git_buf in = GIT_BUF_INIT_CONST(workdir_data, strlen(workdir_data)); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_ODB)); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + + cl_assert_equal_i(BITFLIPPED_AND_REVERSED_DATA_LEN, out.size); + + cl_assert_equal_i( + 0, memcmp(bitflipped_and_reversed_data, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_free(&out); +} + +void test_filter_custom__to_workdir(void) +{ + git_filter_list *fl; + git_buf out = { 0 }; + git_buf in = GIT_BUF_INIT_CONST( + bitflipped_and_reversed_data, BITFLIPPED_AND_REVERSED_DATA_LEN); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE)); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + + cl_assert_equal_i(strlen(workdir_data), out.size); + + cl_assert_equal_i( + 0, memcmp(workdir_data, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_free(&out); +} + +void test_filter_custom__can_register_a_custom_filter_in_the_repository(void) +{ + git_filter_list *fl; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE)); + /* expect: bitflip, reverse, crlf */ + cl_assert_equal_sz(3, git_filter_list_length(fl)); + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herocorp", GIT_FILTER_TO_WORKTREE)); + /* expect: bitflip, reverse */ + cl_assert_equal_sz(2, git_filter_list_length(fl)); + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "heroflip", GIT_FILTER_TO_WORKTREE)); + /* expect: bitflip (because of -reverse) */ + cl_assert_equal_sz(1, git_filter_list_length(fl)); + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "doesntapplytome", GIT_FILTER_TO_WORKTREE)); + /* expect: none */ + cl_assert_equal_sz(0, git_filter_list_length(fl)); + git_filter_list_free(fl); +} diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c index 6dc7800db..0b2d6bf9e 100644 --- a/tests-clar/object/blob/filter.c +++ b/tests-clar/object/blob/filter.c @@ -4,9 +4,10 @@ #include "buf_text.h" static git_repository *g_repo = NULL; -#define NUM_TEST_OBJECTS 9 -static git_oid g_oids[NUM_TEST_OBJECTS]; -static const char *g_raw[NUM_TEST_OBJECTS] = { + +#define CRLF_NUM_TEST_OBJECTS 9 + +static const char *g_crlf_raw[CRLF_NUM_TEST_OBJECTS] = { "", "foo\nbar\n", "foo\rbar\r", @@ -17,19 +18,14 @@ static const char *g_raw[NUM_TEST_OBJECTS] = { "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n", "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" }; -static git_off_t g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, -1, 12 }; -static git_buf_text_stats g_stats[NUM_TEST_OBJECTS] = { - { 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 2, 0, 6, 0 }, - { 0, 0, 2, 0, 0, 6, 0 }, - { 0, 0, 2, 2, 2, 6, 0 }, - { 0, 0, 4, 4, 1, 31, 0 }, - { 0, 1, 1, 2, 1, 9, 5 }, - { GIT_BOM_UTF8, 0, 0, 1, 0, 16, 0 }, - { GIT_BOM_UTF8, 0, 2, 2, 2, 27, 0 }, - { GIT_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 }, + +static git_off_t g_crlf_raw_len[CRLF_NUM_TEST_OBJECTS] = { + -1, -1, -1, -1, -1, 17, -1, -1, 12 }; -static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { + +static git_oid g_crlf_oids[CRLF_NUM_TEST_OBJECTS]; + +static git_buf g_crlf_filtered[CRLF_NUM_TEST_OBJECTS] = { { "", 0, 0 }, { "foo\nbar\n", 0, 8 }, { "foo\rbar\r", 0, 8 }, @@ -41,30 +37,36 @@ static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } }; +static git_buf_text_stats g_crlf_filtered_stats[CRLF_NUM_TEST_OBJECTS] = { + { 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 2, 0, 6, 0 }, + { 0, 0, 2, 0, 0, 6, 0 }, + { 0, 0, 2, 2, 2, 6, 0 }, + { 0, 0, 4, 4, 1, 31, 0 }, + { 0, 1, 1, 2, 1, 9, 5 }, + { GIT_BOM_UTF8, 0, 0, 1, 0, 16, 0 }, + { GIT_BOM_UTF8, 0, 2, 2, 2, 27, 0 }, + { GIT_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 }, +}; + void test_object_blob_filter__initialize(void) { int i; - cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(p_rename( - "empty_standard_repo/.gitted", "empty_standard_repo/.git")); - cl_git_pass(git_repository_open(&g_repo, "empty_standard_repo")); + g_repo = cl_git_sandbox_init("empty_standard_repo"); - for (i = 0; i < NUM_TEST_OBJECTS; i++) { - size_t len = (g_len[i] < 0) ? strlen(g_raw[i]) : (size_t)g_len[i]; - g_len[i] = (git_off_t)len; + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + if (g_crlf_raw_len[i] < 0) + g_crlf_raw_len[i] = strlen(g_crlf_raw[i]); - cl_git_pass( - git_blob_create_frombuffer(&g_oids[i], g_repo, g_raw[i], len) - ); + cl_git_pass(git_blob_create_frombuffer( + &g_crlf_oids[i], g_repo, g_crlf_raw[i], (size_t)g_crlf_raw_len[i])); } } void test_object_blob_filter__cleanup(void) { - git_repository_free(g_repo); - g_repo = NULL; - cl_fixture_cleanup("empty_standard_repo"); + cl_git_sandbox_cleanup(); } void test_object_blob_filter__unfiltered(void) @@ -72,10 +74,15 @@ void test_object_blob_filter__unfiltered(void) int i; git_blob *blob; - for (i = 0; i < NUM_TEST_OBJECTS; i++) { - cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i])); - cl_assert(g_len[i] == git_blob_rawsize(blob)); - cl_assert(memcmp(git_blob_rawcontent(blob), g_raw[i], (size_t)g_len[i]) == 0); + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + size_t raw_len = (size_t)g_crlf_raw_len[i]; + + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); + + cl_assert_equal_sz(raw_len, (size_t)git_blob_rawsize(blob)); + cl_assert_equal_i( + 0, memcmp(g_crlf_raw[i], git_blob_rawcontent(blob), raw_len)); + git_blob_free(blob); } } @@ -87,11 +94,12 @@ void test_object_blob_filter__stats(void) git_buf buf = GIT_BUF_INIT; git_buf_text_stats stats; - for (i = 0; i < NUM_TEST_OBJECTS; i++) { - cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i])); + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); cl_git_pass(git_blob__getbuf(&buf, blob)); git_buf_text_gather_stats(&stats, &buf, false); - cl_assert(memcmp(&g_stats[i], &stats, sizeof(stats)) == 0); + cl_assert_equal_i( + 0, memcmp(&g_crlf_filtered_stats[i], &stats, sizeof(stats))); git_blob_free(blob); } @@ -116,14 +124,15 @@ void test_object_blob_filter__to_odb(void) &fl, g_repo, NULL, "filename.txt", GIT_FILTER_TO_ODB)); cl_assert(fl != NULL); - for (i = 0; i < NUM_TEST_OBJECTS; i++) { - cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i])); + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob)); - cl_assert(!memcmp( - out.ptr, g_crlf_filtered[i].ptr, - min(out.size, g_crlf_filtered[i].size))); + cl_assert_equal_sz(g_crlf_filtered[i].size, out.size); + + cl_assert_equal_i( + 0, memcmp(out.ptr, g_crlf_filtered[i].ptr, out.size)); git_blob_free(blob); } @@ -132,4 +141,3 @@ void test_object_blob_filter__to_odb(void) git_buf_free(&out); git_config_free(cfg); } -