diff --git a/src/backends/hiredis.c b/src/backends/hiredis.c new file mode 100644 index 000000000..ea8322fa7 --- /dev/null +++ b/src/backends/hiredis.c @@ -0,0 +1,202 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "git2/object.h" +#include "hash.h" +#include "odb.h" + +#include "git2/odb_backend.h" + +#ifdef GIT2_HIREDIS_BACKEND + +#include + +typedef struct { + git_odb_backend parent; + + redisContext *db; +} hiredis_backend; + +int hiredis_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) { + hiredis_backend *backend; + int error; + redisReply *reply; + + assert(len_p && type_p && _backend && oid); + + backend = (hiredis_backend *) _backend; + error = GIT_ERROR; + + reply = redisCommand(backend->db, "HMGET %b %s %s", oid->id, GIT_OID_RAWSZ, + "type", "size"); + assert(reply->type != REDIS_REPLY_ERROR); + + if (reply->type == REDIS_REPLY_ARRAY) { + if (reply->element[0]->type != REDIS_REPLY_NIL && + reply->element[0]->type != REDIS_REPLY_NIL) { + *type_p = (git_otype) atoi(reply->element[0]->str); + *len_p = (size_t) atoi(reply->element[1]->str); + error = GIT_SUCCESS; + } else { + error = GIT_ENOTFOUND; + } + } else { + error = GIT_ERROR; + } + + freeReplyObject(reply); + return error; +} + +int hiredis_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) { + hiredis_backend *backend; + int error; + redisReply *reply; + + assert(data_p && len_p && type_p && _backend && oid); + + backend = (hiredis_backend *) _backend; + error = GIT_ERROR; + + reply = redisCommand(backend->db, "HMGET %b %s %s %s", oid->id, GIT_OID_RAWSZ, + "type", "size", "data"); + assert(reply->type != REDIS_REPLY_ERROR); + if (reply->type == REDIS_REPLY_ARRAY) { + if (reply->element[0]->type != REDIS_REPLY_NIL && + reply->element[1]->type != REDIS_REPLY_NIL && + reply->element[2]->type != REDIS_REPLY_NIL) { + *type_p = (git_otype) atoi(reply->element[0]->str); + *len_p = (size_t) atoi(reply->element[1]->str); + *data_p = git__malloc(*len_p); + if (*data_p == NULL) { + error = GIT_ENOMEM; + } else { + memcpy(*data_p, reply->element[2]->str, *len_p); + error = GIT_SUCCESS; + } + } else { + error = GIT_ENOTFOUND; + } + } else { + error = GIT_ERROR; + } + + freeReplyObject(reply); + return error; +} + +int hiredis_backend__exists(git_odb_backend *_backend, const git_oid *oid) { + hiredis_backend *backend; + int found; + redisReply *reply; + + assert(_backend && oid); + + backend = (hiredis_backend *) _backend; + found = 0; + + reply = redisCommand(backend->db, "exists %b", oid->id, GIT_OID_RAWSZ); + assert(reply->type == REDIS_REPLY_ERROR); + if (reply->type != REDIS_REPLY_NIL) + found = 1; + + + freeReplyObject(reply); + return found; +} + +int hiredis_backend__write(git_oid *id, git_odb_backend *_backend, const void *data, size_t len, git_otype type) { + hiredis_backend *backend; + int error; + redisReply *reply; + + assert(id && _backend && data); + + backend = (hiredis_backend *) _backend; + error = GIT_ERROR; + + if ((error = git_odb_hash(id, data, len, type)) < 0) + return error; + + reply = redisCommand(backend->db, "HMSET %b " + "type %d " + "size %d " + "data %b ", id->id, GIT_OID_RAWSZ, + (int) type, len, data, len); + error = reply->type == REDIS_REPLY_ERROR ? GIT_ERROR : GIT_SUCCESS; + + freeReplyObject(reply); + return error; +} + +void hiredis_backend__free(git_odb_backend *_backend) { + hiredis_backend *backend; + assert(_backend); + backend = (hiredis_backend *) _backend; + + redisFree(backend->db); + + free(backend); +} + +int git_odb_backend_hiredis(git_odb_backend **backend_out, const char *host, int port) { + hiredis_backend *backend; + + backend = git__calloc(1, sizeof (hiredis_backend)); + if (backend == NULL) + return GIT_ENOMEM; + + + backend->db = redisConnect(host, port); + if (backend->db->err) + goto cleanup; + + backend->parent.read = &hiredis_backend__read; + backend->parent.read_header = &hiredis_backend__read_header; + backend->parent.write = &hiredis_backend__write; + backend->parent.exists = &hiredis_backend__exists; + backend->parent.free = &hiredis_backend__free; + + *backend_out = (git_odb_backend *) backend; + + return GIT_SUCCESS; +cleanup: + free(backend); + return GIT_ERROR; +} + +#else + +int git_odb_backend_hiredis(git_odb_backend ** GIT_UNUSED(backend_out), + const char *GIT_UNUSED(host), int GIT_UNUSED(port)) { + GIT_UNUSED_ARG(backend_out); + GIT_UNUSED_ARG(host); + GIT_UNUSED_ARG(port); + return GIT_ENOTIMPLEMENTED; +} + + +#endif /* HAVE_HIREDIS */ diff --git a/tests/t14-hiredis.c b/tests/t14-hiredis.c new file mode 100644 index 000000000..c743f7d48 --- /dev/null +++ b/tests/t14-hiredis.c @@ -0,0 +1,123 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "test_lib.h" +#include "odb.h" + +#ifdef GIT2_HIREDIS_BACKEND +#include "t03-data.h" +#include "fileops.h" +#include "git2/odb_backend.h" + + +static int cmp_objects(git_odb_object *odb_obj, git_rawobj *raw) +{ + if (raw->type != git_odb_object_type(odb_obj)) + return -1; + + if (raw->len != git_odb_object_size(odb_obj)) + return -1; + + if ((raw->len > 0) && (memcmp(raw->data, git_odb_object_data(odb_obj), raw->len) != 0)) + return -1; + + return 0; +} + +static git_odb *open_hiredis_odb(void) +{ + git_odb *odb; + git_odb_backend *hiredis; + + if (git_odb_new(&odb) < GIT_SUCCESS) + return NULL; + + if (git_odb_backend_hiredis(&hiredis, "127.0.0.1", 6379) < GIT_SUCCESS) + return NULL; + + if (git_odb_add_backend(odb, hiredis, 0) < GIT_SUCCESS) + return NULL; + + return odb; +} + +#define TEST_WRITE(PTR) {\ + git_odb *db; \ + git_oid id1, id2; \ + git_odb_object *obj; \ + db = open_hiredis_odb(); \ + must_be_true(db != NULL); \ + must_pass(git_oid_mkstr(&id1, PTR.id)); \ + must_pass(git_odb_write(&id2, db, PTR##_obj.data, PTR##_obj.len, PTR##_obj.type)); \ + must_be_true(git_oid_cmp(&id1, &id2) == 0); \ + must_pass(git_odb_read(&obj, db, &id1)); \ + must_pass(cmp_objects(obj, &PTR##_obj)); \ + git_odb_object_close(obj); \ + git_odb_close(db); \ +} + +BEGIN_TEST(hiredis0, "write a commit, read it back (hiredis backend)") + TEST_WRITE(commit); +END_TEST + +BEGIN_TEST(hiredis1, "write a tree, read it back (hiredis backend)") + TEST_WRITE(tree); +END_TEST + +BEGIN_TEST(hiredis2, "write a tag, read it back (hiredis backend)") + TEST_WRITE(tag); +END_TEST + +BEGIN_TEST(hiredis3, "write a zero-byte entry, read it back (hiredis backend)") + TEST_WRITE(zero); +END_TEST + +BEGIN_TEST(hiredis4, "write a one-byte entry, read it back (hiredis backend)") + TEST_WRITE(one); +END_TEST + +BEGIN_TEST(hiredis5, "write a two-byte entry, read it back (hiredis backend)") + TEST_WRITE(two); +END_TEST + +BEGIN_TEST(hiredis6, "write some bytes in an entry, read it back (hiredis backend)") + TEST_WRITE(some); +END_TEST + + +BEGIN_SUITE(hiredis) + ADD_TEST(hiredis0); + ADD_TEST(hiredis1); + ADD_TEST(hiredis2); + ADD_TEST(hiredis3); + ADD_TEST(hiredis4); + ADD_TEST(hiredis5); + ADD_TEST(hiredis6); +END_SUITE + +#else /* no hiredis builtin */ +BEGIN_SUITE(hiredis) + /* empty */ +END_SUITE +#endif diff --git a/tests/test_main.c b/tests/test_main.c index f2a623a48..102688ce1 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -41,6 +41,7 @@ DECLARE_SUITE(tag); DECLARE_SUITE(tree); DECLARE_SUITE(refs); DECLARE_SUITE(sqlite); +DECLARE_SUITE(hiredis); DECLARE_SUITE(repository); DECLARE_SUITE(threads); @@ -59,6 +60,7 @@ static libgit2_suite suite_methods[]= { SUITE_NAME(sqlite), SUITE_NAME(repository), SUITE_NAME(threads), + SUITE_NAME(hiredis) }; #define GIT_SUITE_COUNT (ARRAY_SIZE(suite_methods)) diff --git a/wscript b/wscript index dd9040658..8c6a19d89 100644 --- a/wscript +++ b/wscript @@ -16,7 +16,7 @@ CFLAGS_WIN32_L = ['/RELEASE'] # used for /both/ debug and release builds. # sets the module's checksum in the header. CFLAGS_WIN32_L_DBG = ['/DEBUG'] -ALL_LIBS = ['crypto', 'pthread', 'sqlite3'] +ALL_LIBS = ['crypto', 'pthread', 'sqlite3', 'hiredis'] def options(opt): opt.load('compiler_c') @@ -31,6 +31,8 @@ PPC optimized version (ppc) or the SHA1 functions from OpenSSL (openssl)") help='Select target architecture (ia64, x64, x86, x86_amd64, x86_ia64)') opt.add_option('--with-sqlite', action='store_true', default=False, dest='use_sqlite', help='Enable sqlite support') + opt.add_option('--with-hiredis', action='store_true', default=False, + dest='use_hiredis', help='Enable redis support using hiredis') opt.add_option('--threadsafe', action='store_true', default=False, help='Make libgit2 thread-safe (requires pthreads)') @@ -72,6 +74,12 @@ def configure(conf): lib='sqlite3', uselib_store='sqlite3', install_path=None, mandatory=False): conf.env.DEFINES += ['GIT2_SQLITE_BACKEND'] + # check for hiredis + if conf.options.use_hiredis and conf.check_cc( + lib='hiredis', uselib_store='hiredis', install_path=None, mandatory=False): + conf.env.DEFINES += ['GIT2_HIREDIS_BACKEND'] + + if conf.options.sha1 not in ['openssl', 'ppc', 'builtin']: conf.fatal('Invalid SHA1 option')