diff --git a/apps/gitgud/priv/db/migrations/20190411211844_add_git_objects_table.exs b/apps/gitgud/priv/db/migrations/20190411211844_add_git_objects_table.exs new file mode 100644 index 00000000..0af92208 --- /dev/null +++ b/apps/gitgud/priv/db/migrations/20190411211844_add_git_objects_table.exs @@ -0,0 +1,13 @@ +defmodule GitGud.DB.Migrations.AddGitObjectsTable do + use Ecto.Migration + + def change do + create table("git_objects", primary_key: false) do + add :oid, :binary, primary_key: true + add :repo_id, references("repositories", on_delete: :delete_all), primary_key: true + add :type, :integer, null: false + add :size, :integer, null: false + add :data, :binary + end + end +end diff --git a/apps/gitgud/priv/db/migrations/20190411212320_add_git_references_table.exs b/apps/gitgud/priv/db/migrations/20190411212320_add_git_references_table.exs new file mode 100644 index 00000000..cf6bd2ef --- /dev/null +++ b/apps/gitgud/priv/db/migrations/20190411212320_add_git_references_table.exs @@ -0,0 +1,12 @@ +defmodule GitGud.DB.Migrations.AddGitReferencesTable do + use Ecto.Migration + + def change do + create table("git_references", primary_key: false) do + add :name, :string, primary_key: true + add :repo_id, references("repositories", on_delete: :delete_all), primary_key: true + add :symlink, :string + add :oid, :binary + end + end +end diff --git a/apps/gitrekt/Makefile b/apps/gitrekt/Makefile index d67d93f8..1d87bf2f 100644 --- a/apps/gitrekt/Makefile +++ b/apps/gitrekt/Makefile @@ -1,6 +1,6 @@ ERLANG_PATH ?= $(shell erl -eval 'io:format("~s", [lists:concat([code:root_dir(), "/erts-", erlang:system_info(version), "/include"])])' -s init stop -noshell) CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter -I$(ERLANG_PATH) -LDFLAGS += -lgit2 +LDFLAGS += -lgit2 -lpq ifneq ($(OS),Windows_NT) CFLAGS += -fPIC diff --git a/apps/gitrekt/c_src/geef.c b/apps/gitrekt/c_src/geef.c index c97e69e5..ea512c98 100644 --- a/apps/gitrekt/c_src/geef.c +++ b/apps/gitrekt/c_src/geef.c @@ -216,6 +216,7 @@ static ErlNifFunc geef_funcs[] = { {"repository_init", 2, geef_repository_init, 0}, {"repository_open", 1, geef_repository_open, 0}, + {"repository_open_postgres", 2, geef_repository_open_postgres, 0}, {"repository_discover", 1, geef_repository_discover, 0}, {"repository_bare?", 1, geef_repository_is_bare, 0}, {"repository_empty?", 1, geef_repository_is_empty, 0}, diff --git a/apps/gitrekt/c_src/postgres_backend.c b/apps/gitrekt/c_src/postgres_backend.c new file mode 100644 index 00000000..cdf7f930 --- /dev/null +++ b/apps/gitrekt/c_src/postgres_backend.c @@ -0,0 +1,574 @@ +#include +#include + +#include + +#include +#include +#include +#include + +#include "postgres_backend.h" + +#define GIT_ODB_TABLE_NAME "git_objects" +#define GIT_REFDB_TABLE_NAME "git_references" + +typedef struct { + git_odb_backend parent; + PGconn *conn; + int64_t repo_id; +} postgres_odb_backend; + +typedef struct { + git_refdb_backend parent; + PGconn *conn; + int64_t repo_id; +} postgres_refdb_backend; + +typedef struct { + git_reference_iterator parent; + size_t current; + PGresult *result; + postgres_refdb_backend *backend; +} postgres_refdb_iterator; + +int postgres_odb_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) +{ + printf("postgres_odb_backend__read(%s)\n", git_oid_tostr_s(oid)); + + PGresult *result; + postgres_odb_backend *backend; + + assert(data_p && len_p && type_p && _backend && oid); + backend = (postgres_odb_backend *)_backend; + + const int64_t repo_id = htonll(backend->repo_id); + const char *paramValues[2] = {(char *)&repo_id, oid->id}; + int paramLengths[2] = {sizeof(repo_id), 20}; + int paramFormats[2] = {1, 1}; + + result = PQexecParams(backend->conn, "SELECT type, size, data FROM " GIT_ODB_TABLE_NAME " WHERE repo_id = $1 AND oid = $2", 2, NULL, paramValues, paramLengths, paramFormats, 1); + if(PQresultStatus(result) != PGRES_TUPLES_OK) { + giterr_set_str(GITERR_ODB, PQerrorMessage(backend->conn)); + return GIT_ERROR; + } + + if(PQntuples(result) < 1) { + return GIT_ENOTFOUND; + } + + + *type_p = (git_otype)ntohl(*((int *)PQgetvalue(result, 0, 0))); + *len_p = ntohl(*((size_t *)PQgetvalue(result, 0, 1))); + + *data_p = malloc(*len_p); + if(*data_p == NULL) { + return GITERR_NOMEMORY; + } + + memcpy(*data_p, PQgetvalue(result, 0, 2), *len_p); + + PQclear(result); + return GIT_OK; +} + +int postgres_odb_backend__read_prefix(git_oid *out_oid, void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *short_oid, unsigned int len) +{ + printf("postgres_odb_backend__read_prefix(%s)\n", git_oid_tostr_s(short_oid)); + + if (len >= GIT_OID_HEXSZ) { + /* Just match the full identifier */ + int error = postgres_odb_backend__read(data_p, len_p, type_p, _backend, short_oid); + if (error == 0) + git_oid_cpy(out_oid, short_oid); + + return error; + } else if (len < GIT_OID_HEXSZ) { + return GIT_ERROR; + } +} + +int postgres_odb_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) +{ + printf("postgres_odb_backend__read_header(%s)\n", git_oid_tostr_s(oid)); + + PGresult *result; + postgres_odb_backend *backend; + + assert(len_p && type_p && _backend && oid); + + backend = (postgres_odb_backend *)_backend; + + const int64_t repo_id = htonll(backend->repo_id); + const char *paramValues[2] = {(char *)&repo_id, oid->id}; + int paramLengths[2] = {sizeof(repo_id), 20}; + int paramFormats[2] = {1, 1}; + + result = PQexecParams(backend->conn, "SELECT type, size FROM " GIT_ODB_TABLE_NAME " WHERE repo_id = $1 AND oid = $2", 2, NULL, paramValues, paramLengths, paramFormats, 1); + if(PQresultStatus(result) != PGRES_TUPLES_OK) { + giterr_set_str(GITERR_ODB, PQerrorMessage(backend->conn)); + return GIT_ERROR; + } + + if(PQntuples(result) < 1) { + return GIT_ENOTFOUND; + } + + *type_p = (git_otype)ntohl(*((int *)PQgetvalue(result, 0, 0))); + *len_p = ntohl(*((size_t *)PQgetvalue(result, 0, 1))); + + PQclear(result); + return GIT_OK; +} + +int postgres_odb_backend__exists(git_odb_backend *_backend, const git_oid *oid) +{ + printf("postgres_odb_backend__exists(%s)\n", git_oid_tostr_s(oid)); + + postgres_odb_backend *backend; + int found; + PGresult *result; + + assert(_backend && oid); + + backend = (postgres_odb_backend *)_backend; + found = 0; + + const int64_t repo_id = htonll(backend->repo_id); + const char *paramValues[2] = {(char *)&repo_id, oid->id}; + int paramLengths[2] = {sizeof(repo_id), 20}; + int paramFormats[2] = {1, 1}; + + result = PQexecParams(backend->conn, "SELECT type FROM " GIT_ODB_TABLE_NAME " WHERE repo_id = $1 AND oid = $2", 2, NULL, paramValues, paramLengths, paramFormats, 0); + if(PQresultStatus(result) != PGRES_TUPLES_OK) { + giterr_set_str(GITERR_ODB, PQerrorMessage(backend->conn)); + return GIT_ERROR; + } + + if(PQntuples(result) > 0) { + found = 1; + } + + PQclear(result); + return found; +} + +int postgres_odb_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_otype type) +{ + printf("postgres_odb_backend__write(%s)\n", git_oid_tostr_s(oid)); + + PGresult *result; + postgres_odb_backend *backend; + + assert(oid && _backend && data); + + backend = (postgres_odb_backend *)_backend; + + if (git_odb_hash(oid, data, len, type) < 0) + return GIT_ERROR; + + const int64_t repo_id = htonll(backend->repo_id); + const int type_n = htonl(type); + const int size_n = htonl(len); + const char *paramValues[5] = {oid->id, (char *)&repo_id, (char *)&type_n, (char *)&size_n, (char*)data}; + const int paramLengths[5] = {20, sizeof(repo_id), sizeof(type_n), sizeof(size_n), len}; + const int paramFormats[5] = {1, 1, 1, 1, 1}; + + result = PQexecParams(backend->conn, "INSERT INTO " GIT_ODB_TABLE_NAME " VALUES ($1, $2, $3, $4, $5)", 5, NULL, paramValues, paramLengths, paramFormats, 0); + if(PQresultStatus(result) != PGRES_COMMAND_OK) { + giterr_set_str(GITERR_ODB, PQerrorMessage(backend->conn)); + return GIT_ERROR; + } + + PQclear(result); + return GIT_OK; +} + +void postgres_odb_backend__free(git_odb_backend *_backend) +{ + printf("postgres_odb_backend__free()\n"); + + postgres_odb_backend *backend; + + assert(_backend); + backend = (postgres_odb_backend *)_backend; + + PQfinish(backend->conn); + + free(backend); +} + +int postgres_refdb_backend__exists(int *exists, git_refdb_backend *_backend, const char *ref_name) +{ + printf("postgres_refdb_backend__exists(%s)\n", ref_name); + + PGresult *result; + int found = 0; + postgres_refdb_backend *backend; + + assert(ref_name && _backend); + backend = (postgres_refdb_backend *) _backend; + + const int64_t repo_id = htonll(backend->repo_id); + const char *paramValues[2] = {(char *)&repo_id, ref_name}; + int paramLengths[2] = {sizeof(repo_id), strlen(ref_name)}; + int paramFormats[2] = {1, 0}; + + result = PQexecParams(backend->conn, "SELECT oid FROM " GIT_REFDB_TABLE_NAME " WHERE repo_id = $1 AND name = $2", 2, NULL, paramValues, paramLengths, paramFormats, 1); + if(PQresultStatus(result) != PGRES_TUPLES_OK) + giterr_set_str(GITERR_REFERENCE, PQerrorMessage(backend->conn)); + return GIT_ERROR; + + if(PQntuples(result) > 0) { + found = 1; + } + + PQclear(result); + return found; +} + +int postgres_refdb_backend__lookup(git_reference **out, git_refdb_backend *_backend, const char *ref_name) +{ + printf("postgres_refdb_backend__lookup(%s)\n", ref_name); + + PGresult *result; + git_oid oid; + char *symlink; + postgres_refdb_backend *backend; + + assert(ref_name && _backend); + backend = (postgres_refdb_backend *) _backend; + + const int64_t repo_id = htonll(backend->repo_id); + const char *paramValues[2] = {(char *)&repo_id, ref_name}; + int paramLengths[2] = {sizeof(repo_id), strlen(ref_name)}; + int paramFormats[2] = {1, 0}; + + result = PQexecParams(backend->conn, "SELECT symlink, oid FROM " GIT_REFDB_TABLE_NAME " WHERE repo_id = $1 AND name = $2", 2, NULL, paramValues, paramLengths, paramFormats, 1); + if (PQresultStatus(result) != PGRES_TUPLES_OK) { + giterr_set_str(GITERR_REFERENCE, PQerrorMessage(backend->conn)); + return GIT_ERROR; + } + + if (PQntuples(result) != 1) { + PQclear(result); + return GIT_ENOTFOUND; + } + + symlink = PQgetvalue(result, 0, 0); + if(strlen(symlink) > 0) { + *out = git_reference__alloc_symbolic(ref_name, symlink); + } else { + git_oid_fromraw(&oid, (unsigned char *)PQgetvalue(result, 0, 1)); + *out = git_reference__alloc(ref_name, &oid, NULL); + } + + PQclear(result); + return GIT_OK; + +} + +int postgres_refdb_backend__iterator_next(git_reference **out, git_reference_iterator *_iter) { + printf("postgres_refdb_backend__iterator_next()\n"); + + char* ref_name; + char *symlink; + git_oid oid; + postgres_refdb_backend *backend; + postgres_refdb_iterator *iter; + + assert(_iter); + iter = (postgres_refdb_iterator *) _iter; + + if(iter->current >= PQntuples(iter->result)) + return GIT_ITEROVER; + + ref_name = PQgetvalue(iter->result, iter->current, 0); + + git_oid_fromraw(&oid, (unsigned char *)PQgetvalue(iter->result, iter->current++, 1)); + *out = git_reference__alloc(ref_name, &oid, NULL); + + return GIT_OK; +} + +int postgres_refdb_backend__iterator_next_name(const char **ref_name, git_reference_iterator *_iter) { + printf("postgres_refdb_backend__iterator_next_name()\n"); + + postgres_refdb_iterator *iter; + + assert(_iter); + iter = (postgres_refdb_iterator *) _iter; + + if(iter->current >= PQntuples(iter->result)) + return GIT_ITEROVER; + + *ref_name = PQgetvalue(iter->result, iter->current++, 0); + return GIT_OK; +} + +void postgres_refdb_backend__iterator_free(git_reference_iterator *_iter) { + postgres_refdb_iterator *iter; + + assert(_iter); + iter = (postgres_refdb_iterator *) _iter; + + PQclear(iter->result); + free(iter); +} + +int postgres_refdb_backend__iterator(git_reference_iterator **_iter, struct git_refdb_backend *_backend, const char *glob) +{ + printf("postgres_refdb_backend__iterator(%s)\n", glob); + + PGresult *result; + char *pattern; + char *current_pos; + postgres_refdb_iterator *iterator; + postgres_refdb_backend *backend; + + assert(_backend); + backend = (postgres_refdb_backend *) _backend; + + const int64_t repo_id = htonll(backend->repo_id); + + iterator = calloc(1, sizeof(postgres_refdb_iterator)); + + if(glob) { + pattern = strcpy(malloc(strlen(glob) + 1), glob); + current_pos = strchr(pattern, '%'); + for (char* p = current_pos; (current_pos = strchr(pattern, '*')) != NULL; *current_pos = '%'); + const char *paramValues[2] = {(char *) &repo_id, pattern}; + int paramLengths[2] = {sizeof(backend->repo_id), strlen(pattern)}; + int paramFormats[2] = {1, 0}; + result = PQexecParams(backend->conn, "SELECT name, oid FROM " GIT_REFDB_TABLE_NAME " WHERE repo_id = $1 AND symlink IS NULL AND name LIKE $2", 2, NULL, paramValues, paramLengths, paramFormats, 1); + } else { + const char *paramValues[1] = {(char *) &repo_id}; + int paramLengths[1] = {sizeof(repo_id)}; + int paramFormats[1] = {1}; + result = PQexecParams(backend->conn, "SELECT name, oid FROM " GIT_REFDB_TABLE_NAME " WHERE repo_id = $1 AND symlink IS NULL", 1, NULL, paramValues, paramLengths, paramFormats, 1); + } + + if (PQresultStatus(result) != PGRES_TUPLES_OK) { + giterr_set_str(GITERR_REFERENCE, PQerrorMessage(backend->conn)); + return GIT_ERROR; + } + + iterator->backend = backend; + iterator->current = 0; + iterator->result = result; + iterator->parent.next = &postgres_refdb_backend__iterator_next; + iterator->parent.next_name = &postgres_refdb_backend__iterator_next_name; + iterator->parent.free = &postgres_refdb_backend__iterator_free; + + *_iter = (git_reference_iterator *) iterator; + + return GIT_OK; +} + +int postgres_refdb_backend__write(git_refdb_backend *_backend, const git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old, const char *old_target) +{ + + PGresult *result; + const char *name = git_reference_name(ref); + printf("postgres_refdb_backend__write(%s, %d)\n", name, force); + const git_oid *target; + const char *symbolic_target; + postgres_refdb_backend *backend; + + assert(ref && _backend); + backend = (postgres_refdb_backend *) _backend; + + target = git_reference_target(ref); + symbolic_target = git_reference_symbolic_target(ref); + + const int64_t repo_id = htonll(backend->repo_id); + if (target) { + const char *paramValues[3] = {name, (char *) &repo_id, target->id}; + int paramLengths[3] = {strlen(name), sizeof(repo_id), 20}; + int paramFormats[3] = {0, 1, 1}; + if(force == 1) { + result = PQexecParams(backend->conn, "INSERT INTO " GIT_REFDB_TABLE_NAME " VALUES($1, $2, NULL, $3) ON CONFLICT ON CONSTRAINT git_references_pkey DO UPDATE SET oid = $3, symlink = NULL", 3, NULL, paramValues, paramLengths, paramFormats, 0); + } else { + result = PQexecParams(backend->conn, "INSERT INTO " GIT_REFDB_TABLE_NAME " VALUES($1, $2, NULL, $3) ON CONFLICT ON CONSTRAINT git_references_pkey DO NOTHING", 3, NULL, paramValues, paramLengths, paramFormats, 0); + } + } else { + const char *paramValues[3] = {name, (char *) &repo_id, symbolic_target}; + int paramLengths[3] = {strlen(name), sizeof(repo_id), strlen(symbolic_target)}; + int paramFormats[3] = {0, 1, 0}; + if(force == 1) { + result = PQexecParams(backend->conn, "INSERT INTO " GIT_REFDB_TABLE_NAME " VALUES($1, $2, $3, NULL) ON CONFLICT ON CONSTRAINT git_references_pkey DO UPDATE SET symlink = $3, oid = NULL", 3, NULL, paramValues, paramLengths, paramFormats, 0); + } else { + result = PQexecParams(backend->conn, "INSERT INTO " GIT_REFDB_TABLE_NAME " VALUES($1, $2, $3, NULL) ON CONFLICT ON CONSTRAINT git_references_pkey DO NOTHING, oid = NULL", 3, NULL, paramValues, paramLengths, paramFormats, 0); + } + } + + if(PQresultStatus(result) != PGRES_COMMAND_OK) { + giterr_set_str(GITERR_REFERENCE, PQerrorMessage(backend->conn)); + return GIT_ERROR; + } + + return GIT_OK; +} + +int postgres_refdb_backend__rename(git_reference **out, git_refdb_backend *_backend, const char *old_name, const char *new_name, int force, const git_signature *who, const char *message) +{ + printf("postgres_refdb_backend__rename(%s, %s)", old_name, new_name); + + PGresult *result; + postgres_refdb_backend *backend; + + assert(old_name && new_name && _backend); + backend = (postgres_refdb_backend *) _backend; + + const int64_t repo_id = htonll(backend->repo_id); + const char *paramValues[3] = {new_name, (char *) &repo_id, old_name}; + int paramLengths[3] = {strlen(new_name), sizeof(repo_id), strlen(old_name)}; + int paramFormats[3] = {0, 1, 0}; + + result = PQexecParams(backend->conn, "UPDATE " GIT_REFDB_TABLE_NAME " SET name = $1 WHERE repo_id = $2 AND name = $3", 3, NULL, paramValues, paramLengths, paramFormats, 0); + if(PQresultStatus(result) != PGRES_COMMAND_OK) { + giterr_set_str(GITERR_REFERENCE, PQerrorMessage(backend->conn)); + return GIT_ERROR; + } + + return postgres_refdb_backend__lookup(out, _backend, new_name); +} + +int postgres_refdb_backend__del(git_refdb_backend *_backend, const char *ref_name, const git_oid *old, const char *old_target) +{ + printf("postgres_refdb_backend__del(%s, %s)\n", ref_name, old_target); + + PGresult *result; + postgres_refdb_backend *backend; + + assert(ref_name && _backend); + backend = (postgres_refdb_backend *) _backend; + + const int64_t repo_id = htonll(backend->repo_id); + const char *paramValues[2] = {(char *) &repo_id, ref_name}; + int paramLengths[2] = {sizeof(repo_id), strlen(ref_name)}; + int paramFormats[3] = {1, 0}; + + result = PQexecParams(backend->conn, "DELETE FROM " GIT_REFDB_TABLE_NAME " WHERE repo_id = $1 AND name = $2", 2, NULL, paramValues, paramLengths, paramFormats, 0); + if(PQresultStatus(result) != PGRES_COMMAND_OK) { + giterr_set_str(GITERR_REFERENCE, PQerrorMessage(backend->conn)); + return GIT_ERROR; + } + + return GIT_OK; +} + +void postgres_refdb_backend__free(git_refdb_backend *_backend) +{ + printf("postgres_refdb_backend__free()\n"); + + postgres_odb_backend *backend; + + assert(_backend); + backend = (postgres_refdb_backend *)_backend; + + PQfinish(backend->conn); + + free(backend); +} + +int postgres_refdb_backend__has_log(git_refdb_backend *_backend, const char *refname) +{ + return 0; +} + +int postgres_refdb_backend__ensure_log(git_refdb_backend *_backend, const char *refname) +{ + return GIT_ERROR; +} + +int postgres_refdb_backend__reflog_read(git_reflog **out, git_refdb_backend *_backend, const char *name) +{ + return GIT_ERROR; +} + +int postgres_refdb_backend__reflog_write(git_refdb_backend *_backend, git_reflog *reflog) +{ + return GIT_ERROR; +} + +int postgres_refdb_backend__reflog_rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) +{ + return GIT_ERROR; +} + +int postgres_refdb_backend__reflog_delete(git_refdb_backend *_backend, const char *name) +{ + return GIT_ERROR; +} + +int pq_connect(PGconn **conn, const char *conn_info) +{ + *conn = PQconnectdb(conn_info); + if(!(*conn)) { + return 1; + } + + if(PQstatus(*conn) != CONNECTION_OK) { + PQfinish(*conn); + return 1; + } + + return 0; +} + +int git_odb_backend_postgres(git_odb_backend **backend_out, PGconn *conn, int64_t repo_id) +{ + postgres_odb_backend *backend; + + backend = calloc(1, sizeof (postgres_odb_backend)); + if (backend == NULL) + return GITERR_NOMEMORY; + + backend->conn = conn; + backend->repo_id = repo_id; + backend->parent.version = 1; + backend->parent.read = &postgres_odb_backend__read; + backend->parent.read_prefix = &postgres_odb_backend__read_prefix; + backend->parent.read_header = &postgres_odb_backend__read_header; + backend->parent.exists = &postgres_odb_backend__exists; + backend->parent.write = &postgres_odb_backend__write; + backend->parent.free = &postgres_odb_backend__free; + backend->parent.writestream = NULL; + backend->parent.foreach = NULL; + + *backend_out = (git_odb_backend *) backend; + + return GIT_OK; +} + +int git_refdb_backend_postgres(git_refdb_backend **backend_out, PGconn *conn, int64_t repo_id) +{ + postgres_refdb_backend *backend; + + backend = calloc(1, sizeof(postgres_refdb_backend)); + if (backend == NULL) + return GITERR_NOMEMORY; + + backend->conn = conn; + backend->repo_id = repo_id; + backend->parent.exists = &postgres_refdb_backend__exists; + backend->parent.lookup = &postgres_refdb_backend__lookup; + backend->parent.iterator = &postgres_refdb_backend__iterator; + backend->parent.write = &postgres_refdb_backend__write; + backend->parent.del = &postgres_refdb_backend__del; + backend->parent.rename = &postgres_refdb_backend__rename; + backend->parent.compress = NULL; + backend->parent.free = &postgres_refdb_backend__free; + + backend->parent.has_log = &postgres_refdb_backend__has_log; + backend->parent.ensure_log = &postgres_refdb_backend__ensure_log; + backend->parent.reflog_read = &postgres_refdb_backend__reflog_read; + backend->parent.reflog_write = &postgres_refdb_backend__reflog_write; + backend->parent.reflog_rename = &postgres_refdb_backend__reflog_rename; + backend->parent.reflog_delete = &postgres_refdb_backend__reflog_delete; + + *backend_out = (git_refdb_backend *) backend; + + return GIT_OK; +} diff --git a/apps/gitrekt/c_src/postgres_backend.h b/apps/gitrekt/c_src/postgres_backend.h new file mode 100644 index 00000000..dfbda724 --- /dev/null +++ b/apps/gitrekt/c_src/postgres_backend.h @@ -0,0 +1,11 @@ +#ifndef GEEF_BACKEND_H +#define GEEF_BACKEND_H + +#include +#include + +int pq_connect(PGconn **conn, const char *conn_info); +int git_odb_backend_postgres(git_odb_backend **backend_out, PGconn *conn, int64_t repo_id); +int git_refdb_backend_postgres(git_refdb_backend **backend_out, PGconn *conn, int64_t repo_id); + +#endif diff --git a/apps/gitrekt/c_src/repository.c b/apps/gitrekt/c_src/repository.c index 8eeea073..ea668d76 100644 --- a/apps/gitrekt/c_src/repository.c +++ b/apps/gitrekt/c_src/repository.c @@ -2,6 +2,7 @@ #include "object.h" #include "oid.h" #include "config.h" +#include "postgres_backend.h" #include "geef.h" #include #include @@ -71,6 +72,61 @@ geef_repository_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_tuple2(env, atoms.ok, term_repo); } +ERL_NIF_TERM +geef_repository_open_postgres(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + PGconn *conn; + ErlNifBinary bin; + git_repository *repo; + git_odb *odb; + git_odb_backend *odb_backend; + git_refdb *refdb; + git_refdb_backend *refdb_backend; + int64_t repo_id; + int error; + geef_repository *res_repo; + ERL_NIF_TERM term_repo; + + + if (!enif_get_int64(env, argv[0], &repo_id)) + return enif_make_badarg(env); + + if (!enif_inspect_binary(env, argv[1], &bin)) + return enif_make_badarg(env); + + if (!geef_terminate_binary(&bin)) + return geef_oom(env); + + if (pq_connect(&conn, (char *)bin.data)) + return GIT_ERROR; + + error = git_odb_new(&odb); + if (!error) + error = git_repository_wrap_odb(&repo, odb); + if (!error) + error = git_odb_backend_postgres(&odb_backend, conn, repo_id); + if (!error) + error = git_odb_add_backend(odb, odb_backend, 1); + if (!error) + error = git_refdb_new(&refdb, repo); + if (!error) + error = git_refdb_backend_postgres(&refdb_backend, conn, repo_id); + if (!error) + error = git_refdb_set_backend(refdb, refdb_backend); + if (!error) + git_repository_set_refdb(repo, refdb); + + if (error) + return geef_error(env); + + res_repo = enif_alloc_resource(geef_repository_type, sizeof(geef_repository)); + res_repo->repo = repo; + term_repo = enif_make_resource(env, res_repo); + enif_release_resource(res_repo); + + return enif_make_tuple2(env, atoms.ok, term_repo); +} + ERL_NIF_TERM geef_repository_discover(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { diff --git a/apps/gitrekt/c_src/repository.h b/apps/gitrekt/c_src/repository.h index f70f9054..fcffe066 100644 --- a/apps/gitrekt/c_src/repository.h +++ b/apps/gitrekt/c_src/repository.h @@ -8,6 +8,7 @@ ERL_NIF_TERM geef_repository_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM geef_repository_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM geef_repository_open_postgres(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM geef_repository_discover(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM geef_repository_path(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM geef_repository_workdir(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); diff --git a/apps/gitrekt/lib/gitrekt/git.ex b/apps/gitrekt/lib/gitrekt/git.ex index 1330e586..9042f88b 100644 --- a/apps/gitrekt/lib/gitrekt/git.ex +++ b/apps/gitrekt/lib/gitrekt/git.ex @@ -84,6 +84,15 @@ defmodule GitRekt.Git do raise Code.LoadError, file: @nif_path_lib end + def repository_open(:postgres, repo_id, db_url) do + repository_open_postgres(repo_id, db_url) + end + + @doc false + def repository_open_postgres(_repo_id, _db_url) do + raise Code.LoadError, file: @nif_path_lib + end + @doc """ Returns `true` if `repo` is bare; elsewhise returns `false`. """ diff --git a/apps/gitrekt/lib/gitrekt/wire_protocol.ex b/apps/gitrekt/lib/gitrekt/wire_protocol.ex index 5b4d8f7d..b8070f8e 100644 --- a/apps/gitrekt/lib/gitrekt/wire_protocol.ex +++ b/apps/gitrekt/lib/gitrekt/wire_protocol.ex @@ -4,6 +4,7 @@ defmodule GitRekt.WireProtocol do """ alias GitRekt.Git + alias GitRekt.GitAgent alias GitRekt.Packfile @upload_caps ~w(thin-pack multi_ack multi_ack_detailed) @@ -86,9 +87,9 @@ defmodule GitRekt.WireProtocol do @doc """ Returns a stream describing each ref and it current value. """ - @spec reference_discovery(Git.repo, binary) :: iolist - def reference_discovery(repo, service) do - [reference_head(repo), reference_list(repo), reference_tags(repo)] + @spec reference_discovery(GitAgent.agent, binary) :: iolist + def reference_discovery(agent, service) do + [reference_head(agent), reference_branches(agent), reference_tags(agent)] |> List.flatten() |> Enum.map(&format_ref_line/1) |> List.update_at(0, &(&1 <> "\0" <> server_capabilities(service))) @@ -154,39 +155,29 @@ defmodule GitRekt.WireProtocol do defp server_capabilities("git-upload-pack"), do: Enum.join(@upload_caps, " ") defp server_capabilities("git-receive-pack"), do: Enum.join(@receive_caps, " ") - defp format_ref_line({oid, refname}), do: "#{Git.oid_fmt(oid)} #{refname}" + defp format_ref_line(ref), do: "#{Git.oid_fmt(ref.oid)} #{ref.prefix <> ref.name}" - defp reference_head(repo) do - case Git.reference_resolve(repo, "HEAD") do - {:ok, _refname, _shorthand, oid} -> {oid, "HEAD"} + defp reference_head(agent) do + case GitAgent.head(agent) do + {:ok, head} -> %{head|prefix: "", name: "HEAD"} {:error, _reason} -> [] end end - defp reference_list(repo) do - case Git.reference_stream(repo, "refs/heads/*") do - {:ok, stream} -> Enum.map(stream, fn {refname, _shortand, :oid, oid} -> {oid, refname} end) + defp reference_branches(agent) do + case GitAgent.branches(agent) do + {:ok, stream} -> Enum.to_list(stream) {:error, _reason} -> [] end end - defp reference_tags(repo) do - case Git.reference_stream(repo, "refs/tags/*") do - {:ok, stream} -> Enum.map(stream, &peel_tag_ref(repo, &1)) + defp reference_tags(agent) do + case GitAgent.tags(agent) do + {:ok, stream} -> Enum.to_list(stream) {:error, _reason} -> [] end end - defp peel_tag_ref(repo, {refname, _shorthand, :oid, oid}) do - with {:ok, :tag, tag} <- Git.object_lookup(repo, oid), - {:ok, :commit, ^oid, commit} <- Git.tag_peel(tag), - {:ok, tag_oid} <- Git.object_id(commit) do - [{tag_oid, refname}, {oid, refname <> "^{}"}] - else - {:ok, :commit, _commit} -> {oid, refname} - end - end - defp pkt_stream(data) do Stream.resource(fn -> data end, &pkt_next/1, fn _ -> :ok end) end