-
-
Save animecyc/a24b6c75338d506be188a1724dafccc7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
* | |
* This software consists of voluntary contributions made by many individuals | |
* and is licensed under the MIT license. For more information, see | |
* <http://www.doctrine-project.org>. | |
*/ | |
#include <assert.h> | |
#include <string.h> | |
#include <git2.h> | |
#include <git2/odb.h> | |
#include <git2/sys/odb_backend.h> | |
#include <buffer.h> | |
#include <odb.h> | |
#include <libpq-fe.h> | |
#define GIT2_ODB_TABLE_NAME "git_objects" | |
typedef struct { | |
git_odb_backend parent; | |
PGconn *db; | |
char *repo_name; | |
} postgres_odb_backend; | |
void postgres_odb_exception_raise(void) | |
{ | |
VALUE err_obj, rb_mRugged, rb_eRepoError; | |
const git_error *error; | |
const char *err_message; | |
error = giterr_last(); | |
if (error && error->message) { | |
err_message = error->message; | |
} else { | |
err_message = "Unknown Error"; | |
} | |
rb_mRugged = rb_const_get(rb_cObject, rb_intern("Rugged")); | |
rb_eRepoError = rb_const_get(rb_mRugged, rb_intern("RepositoryError")); | |
err_obj = rb_exc_new2(rb_eRepoError, err_message); | |
giterr_clear(); | |
rb_exc_raise(err_obj); | |
} | |
static inline void postgres_odb_exception_check(int errorcode) | |
{ | |
if (errorcode < 0) | |
postgres_odb_exception_raise(); | |
} | |
static int postgres_odb_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) | |
{ | |
postgres_odb_backend *backend = (postgres_odb_backend *)_backend; | |
PGresult *result; | |
const char* paramValues[2]; | |
int paramLengths[2]; | |
int binary[2] = {0, 1}; | |
int error = GIT_ERROR; | |
assert(len_p && type_p && backend && oid); | |
paramValues[0] = (char *)backend->repo_name; | |
paramValues[1] = (char *)oid->id; | |
paramLengths[0] = strlen(backend->repo_name); | |
paramLengths[1] = 20; | |
result = PQexecPrepared(backend->db, "git_odb_read_header", 2, paramValues, paramLengths, binary, 1); | |
if (PQresultStatus(result) != PGRES_TUPLES_OK) { | |
giterr_set(GITERR_INVALID, "Error reading object in Postgres ODB backend: %s", PQerrorMessage(backend->db)); | |
postgres_odb_exception_check(GIT_ERROR); | |
error = GIT_ERROR; | |
} else { | |
if (PQntuples(result) == 0) { | |
error = git_odb__error_notfound("Could not find object in Postgres ODB backend", oid); | |
postgres_odb_exception_check(error); | |
} else { | |
*type_p = (git_otype)ntohl(*((uint32_t *)PQgetvalue(result, 0, 0))); | |
*len_p = (size_t)ntohl(*((uint32_t *)PQgetvalue(result, 0, 1))); | |
error = GIT_OK; | |
} | |
} | |
PQclear(result); | |
return error; | |
} | |
static int postgres_odb_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) | |
{ | |
postgres_odb_backend *backend = (postgres_odb_backend *)_backend; | |
PGresult *result; | |
const char* paramValues[2]; | |
int paramLengths[2]; | |
int binary[2] = {0, 1}; | |
int error = GIT_ERROR; | |
assert(data_p && len_p && type_p && backend && oid); | |
paramValues[0] = (char *)backend->repo_name; | |
paramValues[1] = (char *)oid->id; | |
paramLengths[0] = strlen(backend->repo_name); | |
paramLengths[1] = 20; | |
result = PQexecPrepared(backend->db, "git_odb_read", 2, paramValues, paramLengths, binary, 1); | |
if (PQresultStatus(result) != PGRES_TUPLES_OK) { | |
giterr_set(GITERR_INVALID, "Error reading object in Postgres ODB backend: %s", PQerrorMessage(backend->db)); | |
postgres_odb_exception_check(GIT_ERROR); | |
error = GIT_ERROR; | |
} else { | |
if (PQntuples(result) == 0) { | |
error = git_odb__error_notfound("Could not find object in Postgres ODB backend", oid); | |
postgres_odb_exception_check(error); | |
} else { | |
*type_p = (git_otype)ntohl(*((uint32_t *)PQgetvalue(result, 0, 0))); | |
*len_p = (size_t)ntohl(*((uint32_t *)PQgetvalue(result, 0, 1))); | |
*data_p = malloc(*len_p); | |
if (*data_p == NULL) { | |
error = GITERR_NOMEMORY; | |
} else { | |
memcpy(*data_p, PQgetvalue(result, 0, 2), *len_p); | |
error = GIT_OK; | |
} | |
} | |
} | |
PQclear(result); | |
return error; | |
} | |
static 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, | |
size_t len) | |
{ | |
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 == GIT_OK) | |
git_oid_cpy(out_oid, short_oid); | |
return error; | |
} else if (len < GIT_OID_HEXSZ) { | |
return git_odb__error_ambiguous("prefix length too short"); | |
} else { | |
return 0; | |
} | |
} | |
static int postgres_odb_backend__exists(git_odb_backend *_backend, const git_oid *oid) | |
{ | |
postgres_odb_backend *backend = (postgres_odb_backend *)_backend; | |
PGresult *result; | |
const char* paramValues[2]; | |
int paramLengths[2]; | |
int binary[2] = {0, 1}; | |
int found = 0; | |
assert(backend && oid); | |
paramValues[0] = (char *)backend->repo_name; | |
paramValues[1] = (char *)oid->id; | |
paramLengths[0] = strlen(backend->repo_name); | |
paramLengths[1] = 20; | |
result = PQexecPrepared(backend->db, "git_odb_read_header", 2, paramValues, paramLengths, binary, 0); | |
found = PQntuples(result); | |
if (PQresultStatus(result) != PGRES_TUPLES_OK) { | |
giterr_set(GITERR_INVALID, "Error checking for existence of object in Postgres ODB backend: %s", PQerrorMessage(backend->db)); | |
PQclear(result); | |
postgres_odb_exception_check(GIT_ERROR); | |
} | |
PQclear(result); | |
return found; | |
} | |
static int postgres_odb_backend__write(git_odb_backend *_backend, git_oid *id, const void *data, size_t len, git_otype type) | |
{ | |
int error = 0; | |
postgres_odb_backend *backend = (postgres_odb_backend *)_backend; | |
PGresult *result; | |
const char *paramValues[5]; | |
int paramLengths[5]; | |
int binary[5] = {0, 1, 1, 1, 1}; | |
int type_int = htonl((int)type); | |
int len_int = htonl(len); | |
assert(id && backend && data); | |
if ((error = git_odb_hash(id, data, len, type)) < 0) | |
return error; | |
paramValues[0] = (char *)backend->repo_name; | |
paramValues[1] = (char *)id->id; | |
paramValues[2] = (char *)&type_int; | |
paramValues[3] = (char *)&len_int; | |
paramValues[4] = data; | |
paramLengths[0] = strlen(backend->repo_name); | |
paramLengths[1] = 20; | |
paramLengths[2] = sizeof(type_int); | |
paramLengths[3] = sizeof(len_int); | |
paramLengths[4] = len; | |
result = PQexecPrepared(backend->db, "git_odb_write", 5, paramValues, paramLengths, binary, 0); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_INVALID, "Error writing object to Postgres ODB backend: %s", PQerrorMessage(backend->db)); | |
PQclear(result); | |
postgres_odb_exception_check(GIT_ERROR); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
return GIT_OK; | |
} | |
static void postgres_odb_backend__free(git_odb_backend *_backend) | |
{ | |
postgres_odb_backend *backend = (postgres_odb_backend *)_backend; | |
assert(backend); | |
free(backend); | |
} | |
static int create_table(PGconn *db) | |
{ | |
PGresult *result; | |
static const char *sql_create = | |
"CREATE TABLE " GIT2_ODB_TABLE_NAME " (" | |
"repo text NOT NULL," | |
"oid bytea NOT NULL," | |
"type integer NOT NULL," | |
"size integer NOT NULL," | |
"data bytea," | |
"PRIMARY KEY (repo, oid));"; | |
result = PQexec(db, sql_create); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
PQclear(result); | |
giterr_set(GITERR_ODB, "Error creating table for Postgres ODB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
return GIT_OK; | |
} | |
static int init_db(PGconn *db) | |
{ | |
git_buf sql_check = GIT_BUF_INIT; | |
PGresult *result; | |
int error = GIT_OK; | |
git_buf_printf(&sql_check, "SELECT 1 FROM information_schema.tables WHERE table_catalog='%s' AND table_schema='public' AND table_name='%s';", PQdb(db), GIT2_ODB_TABLE_NAME); | |
result = PQexec(db, git_buf_cstr(&sql_check)); | |
if (PQresultStatus(result) != PGRES_TUPLES_OK) { | |
giterr_set(GITERR_ODB, "Error establishing existence of table for Postgres ODB backend: %s", PQerrorMessage(db)); | |
error = GIT_ERROR; | |
} else { | |
if (PQntuples(result) == 0) { | |
error = create_table(db); | |
} | |
} | |
PQclear(result); | |
return error; | |
} | |
static int init_statements(PGconn *db) | |
{ | |
PGresult *result; | |
static const char *sql_read = | |
"SELECT type, size, data FROM " GIT2_ODB_TABLE_NAME " WHERE repo = $1::text AND oid = $2::bytea;"; | |
static const char *sql_read_header = | |
"SELECT type, size FROM " GIT2_ODB_TABLE_NAME " WHERE repo = $1::text AND oid = $2::bytea;"; | |
static const char *sql_write = | |
"INSERT INTO " GIT2_ODB_TABLE_NAME " VALUES ($1::text, $2::bytea, $3::integer, $4::integer, $5::bytea);"; | |
result = PQprepare(db, "git_odb_read", sql_read, 0, NULL); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
result = PQprepare(db, "git_odb_read_header", sql_read_header, 0, NULL); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
result = PQprepare(db, "git_odb_write", sql_write, 0, NULL); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
return GIT_OK; | |
} | |
void parse_conninfo_postgres( | |
char *out, | |
char *database, | |
char *username, | |
char *password, | |
char *hostname, | |
int port) | |
{ | |
git_buf conninfo = GIT_BUF_INIT; | |
git_buf_printf(&conninfo, "client_encoding=UTF8 dbname=%s", database); | |
if (username) | |
git_buf_printf(&conninfo, " user=%s", username); | |
if (password) | |
git_buf_printf(&conninfo, " password=%s", password); | |
if (hostname) { | |
if (strcmp(hostname, "localhost") == 0) { | |
git_buf_printf(&conninfo, " hostaddr=%s", "127.0.0.1"); | |
} else { | |
git_buf_printf(&conninfo, " hostaddr=%s", hostname); | |
} | |
} | |
if (port > 0) | |
git_buf_printf(&conninfo, " port=%d", port); | |
git_buf_copy_cstr(out, git_buf_len(&conninfo) + 1, &conninfo); | |
git_buf_free(&conninfo); | |
} | |
int prep_odb(PGconn *conn) | |
{ | |
int error = GIT_OK; | |
error = init_db(conn); | |
error = init_statements(conn); | |
return error; | |
} | |
int git_odb_backend_postgres( | |
git_odb_backend **backend_out, | |
PGconn *conn, | |
char *repo_name) | |
{ | |
postgres_odb_backend *backend; | |
backend = calloc(1, sizeof(postgres_odb_backend)); | |
if (backend == NULL) | |
return -1; | |
backend->parent.version = GIT_ODB_BACKEND_VERSION; | |
backend->db = conn; | |
backend->repo_name = repo_name; | |
backend->parent.read = &postgres_odb_backend__read; | |
backend->parent.write = &postgres_odb_backend__write; | |
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.free = &postgres_odb_backend__free; | |
*backend_out = (git_odb_backend *)backend; | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
* | |
* This software consists of voluntary contributions made by many individuals | |
* and is licensed under the MIT license. For more information, see | |
* <http://www.doctrine-project.org>. | |
*/ | |
#include <assert.h> | |
#include <string.h> | |
#include <git2.h> | |
#include <git2/tag.h> | |
#include <git2/buffer.h> | |
#include <git2/object.h> | |
#include <git2/refdb.h> | |
#include <git2/errors.h> | |
#include <git2/sys/refdb_backend.h> | |
#include <git2/sys/refs.h> | |
#include <git2/sys/reflog.h> | |
#include <refs.h> | |
#include <iterator.h> | |
#include <refdb.h> | |
#include <fnmatch.h> | |
#include <pool.h> | |
#include <buffer.h> | |
#include <libpq-fe.h> | |
#define GIT2_REFDB_TABLE_NAME "git_refs" | |
#define GIT_SYMREF "ref: " | |
typedef struct postgres_refdb_backend { | |
git_refdb_backend parent; | |
git_repository *repo; | |
PGconn *db; | |
char *repo_name; | |
} postgres_refdb_backend; | |
void postgres_refdb_exception_raise(void) | |
{ | |
VALUE err_obj, rb_mRugged, rb_eRepoError; | |
const git_error *error; | |
const char *err_message; | |
error = giterr_last(); | |
if (error && error->message) { | |
err_message = error->message; | |
} else { | |
err_message = "Unknown Error"; | |
} | |
rb_mRugged = rb_const_get(rb_cObject, rb_intern("Rugged")); | |
rb_eRepoError = rb_const_get(rb_mRugged, rb_intern("RepositoryError")); | |
err_obj = rb_exc_new2(rb_eRepoError, err_message); | |
giterr_clear(); | |
rb_exc_raise(err_obj); | |
} | |
static inline void postgres_refdb_exception_check(int errorcode) | |
{ | |
if (errorcode < 0) | |
postgres_refdb_exception_raise(); | |
} | |
static int ref_error_notfound(const char *name) | |
{ | |
giterr_set(GITERR_REFERENCE, "Reference not found: %s", name); | |
return GIT_ENOTFOUND; | |
} | |
static const char *parse_symbolic(git_buf *ref_content) | |
{ | |
const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); | |
const char *refname_start; | |
refname_start = (const char *)git_buf_cstr(ref_content); | |
if (git_buf_len(ref_content) < header_len + 1) { | |
giterr_set(GITERR_REFERENCE, "Corrupted reference"); | |
return NULL; | |
} | |
/* | |
* Assume we have already checked for the header | |
* before calling this function | |
*/ | |
refname_start += header_len; | |
return refname_start; | |
} | |
static int parse_oid(git_oid *oid, const char *filename, git_buf *ref_content) | |
{ | |
const char *str = git_buf_cstr(ref_content); | |
if (git_buf_len(ref_content) < GIT_OID_HEXSZ) | |
goto corrupted; | |
/* we need to get 40 OID characters from the file */ | |
if (git_oid_fromstr(oid, str) < 0) | |
goto corrupted; | |
/* If the file is longer than 40 chars, the 41st must be a space */ | |
str += GIT_OID_HEXSZ; | |
if (*str == '\0' || git__isspace(*str)) | |
return 0; | |
corrupted: | |
giterr_set(GITERR_REFERENCE, "Corrupted reference"); | |
return -1; | |
} | |
static int postgres_refdb_backend__exists( | |
int *exists, | |
git_refdb_backend *_backend, | |
const char *ref_name) | |
{ | |
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend; | |
PGresult *result; | |
const char* paramValues[2]; | |
int paramLengths[2]; | |
int binary[2] = {0, 0}; | |
assert(backend); | |
*exists = 0; | |
paramValues[0] = (char *)backend->repo_name; | |
paramValues[1] = ref_name; | |
paramLengths[0] = strlen(backend->repo_name); | |
paramLengths[1] = strlen(ref_name); | |
result = PQexecPrepared(backend->db, "git_refdb_read", 2, paramValues, paramLengths, binary, 0); | |
if (PQresultStatus(result) != PGRES_TUPLES_OK) { | |
giterr_set(GITERR_INVALID, "Error checking for reference in Postgres RefDB backend: %s", PQerrorMessage(backend->db)); | |
postgres_refdb_exception_check(GIT_ERROR); | |
} else { | |
if (PQntuples(result) > 0) { | |
*exists = 1; | |
} | |
} | |
PQclear(result); | |
return 0; | |
} | |
static int loose_lookup( | |
git_reference **out, | |
postgres_refdb_backend *backend, | |
const char *ref_name) | |
{ | |
git_buf ref_buf = GIT_BUF_INIT; | |
PGresult *result; | |
const char* paramValues[2]; | |
int paramLengths[2]; | |
int binary[2] = {0, 0}; | |
int error = 0; | |
paramValues[0] = (char *)backend->repo_name; | |
paramValues[1] = ref_name; | |
paramLengths[0] = strlen(backend->repo_name); | |
paramLengths[1] = strlen(ref_name); | |
result = PQexecPrepared(backend->db, "git_refdb_read", 2, paramValues, paramLengths, binary, 0); | |
if (PQresultStatus(result) != PGRES_TUPLES_OK) { | |
giterr_set(GITERR_INVALID, "Error reading reference in Postgres RefDB backend: %s", PQerrorMessage(backend->db)); | |
postgres_refdb_exception_check(GIT_ERROR); | |
error = GIT_ERROR; | |
} else { | |
if (PQntuples(result) == 0) { | |
error = ref_error_notfound(ref_name); | |
} else { | |
char *raw_ref = (char *)PQgetvalue(result, 0, 0); | |
git_buf_set(&ref_buf, raw_ref, strlen(raw_ref)); | |
if (git__prefixcmp(git_buf_cstr(&ref_buf), GIT_SYMREF) == 0) { | |
const char *target; | |
git_buf_rtrim(&ref_buf); | |
if (!(target = parse_symbolic(&ref_buf))) | |
error = -1; | |
else if (out != NULL) | |
*out = git_reference__alloc_symbolic(ref_name, target); | |
} else { | |
git_oid oid; | |
if (!(error = parse_oid(&oid, ref_name, &ref_buf)) && out != NULL) | |
*out = git_reference__alloc(ref_name, &oid, NULL); | |
} | |
} | |
} | |
git_buf_free(&ref_buf); | |
PQclear(result); | |
return error; | |
} | |
static int postgres_refdb_backend__lookup( | |
git_reference **out, | |
git_refdb_backend *_backend, | |
const char *ref_name) | |
{ | |
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend; | |
assert(backend); | |
return loose_lookup(out, backend, ref_name); | |
} | |
typedef struct { | |
git_reference_iterator parent; | |
char *glob; | |
git_pool pool; | |
git_vector loose; | |
size_t loose_pos; | |
} postgres_refdb_iter; | |
static void postgres_refdb_backend__iterator_free(git_reference_iterator *_iter) | |
{ | |
postgres_refdb_iter *iter = (postgres_refdb_iter *) _iter; | |
git_vector_free(&iter->loose); | |
git_pool_clear(&iter->pool); | |
git__free(iter); | |
} | |
static int iter_load_loose_paths(postgres_refdb_backend *backend, postgres_refdb_iter *iter) | |
{ | |
PGresult *result; | |
const char *paramValues[1]; | |
int paramLengths[1]; | |
int binary[1] = {0}; | |
int error = GIT_OK; | |
paramValues[0] = backend->repo_name; | |
paramLengths[0] = strlen(backend->repo_name); | |
result = PQexecPrepared(backend->db, "git_refdb_read_all", 1, paramValues, paramLengths, binary, 0); | |
if (PQresultStatus(result) != PGRES_TUPLES_OK) { | |
giterr_set(GITERR_ODB, "Error reading references from Postgres RefDB backend: %s", PQerrorMessage(backend->db)); | |
error = GIT_ERROR; | |
postgres_refdb_exception_check(error); | |
} else { | |
int i; | |
for (i = 0; i < PQntuples(result); i++) { | |
char *ref_dup; | |
char *ref_name = PQgetvalue(result, i, 0); | |
if (git__prefixcmp(ref_name, GIT_REFS_DIR) != 0) | |
continue; | |
if (git__suffixcmp(ref_name, ".lock") == 0 || | |
(iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0)) | |
continue; | |
ref_dup = git_pool_strdup(&iter->pool, ref_name); | |
if (!ref_dup) | |
error = -1; | |
else | |
error = git_vector_insert(&iter->loose, ref_dup); | |
} | |
} | |
PQclear(result); | |
return error; | |
} | |
static int postgres_refdb_backend__iterator_next( | |
git_reference **out, git_reference_iterator *_iter) | |
{ | |
int error = GIT_ITEROVER; | |
postgres_refdb_iter *iter = (postgres_refdb_iter *)_iter; | |
postgres_refdb_backend *backend = (postgres_refdb_backend *)iter->parent.db->backend; | |
while (iter->loose_pos < iter->loose.length) { | |
const char *path = git_vector_get(&iter->loose, iter->loose_pos++); | |
if (loose_lookup(out, backend, path) == 0) | |
return 0; | |
giterr_clear(); | |
} | |
return error; | |
} | |
static int postgres_refdb_backend__iterator_next_name( | |
const char **out, git_reference_iterator *_iter) | |
{ | |
int error = GIT_ITEROVER; | |
postgres_refdb_iter *iter = (postgres_refdb_iter *)_iter; | |
postgres_refdb_backend *backend = (postgres_refdb_backend *)iter->parent.db->backend; | |
while (iter->loose_pos < iter->loose.length) { | |
const char *path = git_vector_get(&iter->loose, iter->loose_pos++); | |
if (loose_lookup(NULL, backend, path) == 0) { | |
*out = path; | |
return 0; | |
} | |
giterr_clear(); | |
} | |
return error; | |
} | |
static int postgres_refdb_backend__iterator( | |
git_reference_iterator **out, git_refdb_backend *_backend, const char *glob) | |
{ | |
postgres_refdb_iter *iter; | |
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend; | |
assert(backend); | |
iter = git__calloc(1, sizeof(postgres_refdb_iter)); | |
GITERR_CHECK_ALLOC(iter); | |
if (git_pool_init(&iter->pool, 1, 0) < 0 || | |
git_vector_init(&iter->loose, 8, NULL) < 0) | |
goto fail; | |
if (glob != NULL && | |
(iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) | |
goto fail; | |
iter->parent.next = postgres_refdb_backend__iterator_next; | |
iter->parent.next_name = postgres_refdb_backend__iterator_next_name; | |
iter->parent.free = postgres_refdb_backend__iterator_free; | |
if (iter_load_loose_paths(backend, iter) < 0) | |
goto fail; | |
*out = (git_reference_iterator *)iter; | |
return 0; | |
fail: | |
postgres_refdb_backend__iterator_free((git_reference_iterator *)iter); | |
return -1; | |
} | |
static int reference_path_available( | |
postgres_refdb_backend *backend, | |
const char *new_ref, | |
const char* old_ref, | |
int force) | |
{ | |
if (!force) { | |
int exists; | |
if (postgres_refdb_backend__exists(&exists, (git_refdb_backend *)backend, new_ref) < 0) | |
return -1; | |
if (exists) { | |
giterr_set(GITERR_REFERENCE, | |
"Failed to write reference '%s': a reference with " | |
"that name already exists.", new_ref); | |
return GIT_EEXISTS; | |
} | |
} | |
return 0; | |
} | |
static int postgres_refdb_backend__write( | |
git_refdb_backend *_backend, | |
const git_reference *ref, | |
int force, | |
const git_signature *who, | |
const char *message) | |
{ | |
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend; | |
PGresult *result; | |
const char *paramValues[3]; | |
int paramLengths[3]; | |
int binary[3] = {0, 0, 0}; | |
int error; | |
assert(backend); | |
error = reference_path_available(backend, ref->name, NULL, force); | |
if (error < 0) | |
return error; | |
paramValues[0] = backend->repo_name; | |
paramValues[1] = ref->name; | |
paramLengths[0] = strlen(backend->repo_name); | |
paramLengths[1] = strlen(ref->name); | |
if (ref->type == GIT_REF_OID) { | |
char oid[GIT_OID_HEXSZ + 1]; | |
git_oid_nfmt(oid, sizeof(oid), &ref->target.oid); | |
paramValues[2] = (char *)oid; | |
paramLengths[2] = strlen((char *)oid); | |
} else if (ref->type == GIT_REF_SYMBOLIC) { | |
char *symbolic_ref = malloc(strlen(GIT_SYMREF)+strlen(ref->target.symbolic)+1); | |
strcpy(symbolic_ref, GIT_SYMREF); | |
strcat(symbolic_ref, ref->target.symbolic); | |
paramValues[2] = (char *)symbolic_ref; | |
paramLengths[2] = strlen((char *)symbolic_ref); | |
} | |
// Kinda sucks that we have to do a lookup for each write. Would be better to detect the unique key constraint error and attempt an update instead. | |
if (postgres_refdb_backend__lookup(NULL, _backend, ref->name) < 0) { | |
result = PQexecPrepared(backend->db, "git_refdb_write", 3, paramValues, paramLengths, binary, 0); | |
} else { | |
result = PQexecPrepared(backend->db, "git_refdb_update", 3, paramValues, paramLengths, binary, 0); | |
} | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error writing reference to Postgres RefDB backend: %s", PQerrorMessage(backend->db)); | |
error = GIT_ERROR; | |
postgres_refdb_exception_check(error); | |
} else { | |
error = GIT_OK; | |
} | |
PQclear(result); | |
return error; | |
} | |
static int postgres_refdb_backend__delete(git_refdb_backend *_backend, const char *name) | |
{ | |
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend; | |
PGresult *result; | |
const char *paramValues[2]; | |
int paramLengths[2]; | |
int binary[2] = {0, 0}; | |
int error; | |
assert(backend && name); | |
paramValues[0] = backend->repo_name; | |
paramValues[1] = name; | |
paramLengths[0] = strlen(backend->repo_name); | |
paramLengths[1] = strlen(name); | |
result = PQexecPrepared(backend->db, "git_refdb_delete", 2, paramValues, paramLengths, binary, 0); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error deleting reference in Postgres RefDB backend: %s", PQerrorMessage(backend->db)); | |
error = GIT_ERROR; | |
postgres_refdb_exception_check(error); | |
} else { | |
error = GIT_OK; | |
} | |
PQclear(result); | |
return error; | |
} | |
static 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) | |
{ | |
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend; | |
git_reference *old, *new; | |
int error; | |
assert(backend); | |
if ((error = reference_path_available( | |
backend, new_name, old_name, force)) < 0 || | |
(error = postgres_refdb_backend__lookup(&old, _backend, old_name)) < 0) | |
return error; | |
if ((error = postgres_refdb_backend__delete(_backend, old_name)) < 0) { | |
git_reference_free(old); | |
return error; | |
} | |
new = git_reference__set_name(old, new_name); | |
if (!new) { | |
git_reference_free(old); | |
return -1; | |
} | |
if ((error = postgres_refdb_backend__write(_backend, new, force, who, message)) > 0) { | |
git_reference_free(new); | |
return error; | |
} | |
*out = new; | |
return GIT_OK; | |
} | |
static int postgres_refdb_backend__compress(git_refdb_backend *_backend) | |
{ | |
return 0; | |
} | |
static int postgres_refdb_backend__has_log(git_refdb_backend *_backend, const char *name) | |
{ | |
return -1; | |
} | |
static int postgres_refdb_backend__ensure_log(git_refdb_backend *_backend, const char *name) | |
{ | |
return 0; | |
} | |
static void postgres_refdb_backend__free(git_refdb_backend *_backend) | |
{ | |
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend; | |
assert(backend); | |
free(backend); | |
} | |
static int postgres_refdb_backend__reflog_read(git_reflog **out, git_refdb_backend *_backend, const char *name) | |
{ | |
return 0; | |
} | |
static int postgres_refdb_backend__reflog_write(git_refdb_backend *_backend, git_reflog *reflog) | |
{ | |
return 0; | |
} | |
static int postgres_refdb_backend__reflog_rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) | |
{ | |
return 0; | |
} | |
static int postgres_refdb_backend__reflog_delete(git_refdb_backend *_backend, const char *name) | |
{ | |
return 0; | |
} | |
static int create_table(PGconn *db) | |
{ | |
PGresult *result; | |
static const char *sql_create = | |
"CREATE TABLE " GIT2_REFDB_TABLE_NAME " (" | |
"repo text NOT NULL," | |
"refname text NOT NULL," | |
"ref text NOT NULL," | |
"PRIMARY KEY (repo, refname));"; | |
result = PQexec(db, sql_create); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
PQclear(result); | |
giterr_set(GITERR_ODB, "Error creating table for Postgres RefDB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
return GIT_OK; | |
} | |
static int init_db(PGconn *db) | |
{ | |
git_buf sql_check = GIT_BUF_INIT; | |
PGresult *result; | |
int error = GIT_OK; | |
git_buf_printf(&sql_check, "SELECT 1 FROM information_schema.tables WHERE table_catalog='%s' AND table_schema='public' AND table_name='%s';", PQdb(db), GIT2_REFDB_TABLE_NAME); | |
result = PQexec(db, git_buf_cstr(&sql_check)); | |
if (PQresultStatus(result) != PGRES_TUPLES_OK) { | |
giterr_set(GITERR_ODB, "Error establishing existence of table for Postgres RefDB backend: %s", PQerrorMessage(db)); | |
error = GIT_ERROR; | |
} else { | |
if (PQntuples(result) == 0) { | |
error = create_table(db); | |
} | |
} | |
PQclear(result); | |
return error; | |
} | |
static int init_statements(PGconn *db) | |
{ | |
PGresult *result; | |
static const char *sql_read = | |
"SELECT ref FROM " GIT2_REFDB_TABLE_NAME " WHERE repo = $1::text AND refname = $2::text;"; | |
static const char *sql_read_all = | |
"SELECT refname FROM " GIT2_REFDB_TABLE_NAME " WHERE repo = $1::text;"; | |
static const char *sql_write = | |
"INSERT INTO " GIT2_REFDB_TABLE_NAME " VALUES ($1::text, $2::text, $3::text);"; | |
static const char *sql_update = | |
"UPDATE " GIT2_REFDB_TABLE_NAME " SET ref = $3::text WHERE repo = $1::text AND refname = $2::text;"; | |
static const char *sql_delete = | |
"DELETE FROM " GIT2_REFDB_TABLE_NAME " WHERE repo = $1::text AND refname = $2::text;"; | |
result = PQprepare(db, "git_refdb_read", sql_read, 0, NULL); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
result = PQprepare(db, "git_refdb_read_all", sql_read_all, 0, NULL); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
result = PQprepare(db, "git_refdb_write", sql_write, 0, NULL); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
result = PQprepare(db, "git_refdb_update", sql_update, 0, NULL); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
result = PQprepare(db, "git_refdb_delete", sql_delete, 0, NULL); | |
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | |
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db)); | |
return GIT_ERROR; | |
} | |
PQclear(result); | |
return GIT_OK; | |
} | |
int prep_refdb(PGconn *conn) | |
{ | |
int error = GIT_OK; | |
error = init_db(conn); | |
error = init_statements(conn); | |
return error; | |
} | |
int git_refdb_backend_postgres( | |
git_refdb_backend **backend_out, | |
git_repository *repository, | |
PGconn *conn, | |
char *repo_name) | |
{ | |
postgres_refdb_backend *backend; | |
backend = calloc(1, sizeof(postgres_refdb_backend)); | |
if (backend == NULL) | |
return -1; | |
backend->repo = repository; | |
backend->db = conn; | |
backend->repo_name = repo_name; | |
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__delete; | |
backend->parent.rename = &postgres_refdb_backend__rename; | |
backend->parent.compress = &postgres_refdb_backend__compress; | |
backend->parent.has_log = &postgres_refdb_backend__has_log; | |
backend->parent.ensure_log = &postgres_refdb_backend__ensure_log; | |
backend->parent.free = &postgres_refdb_backend__free; | |
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; | |
git_reference *head; | |
if (postgres_refdb_backend__lookup(&head, *backend_out, GIT_HEAD_FILE) < 0) { | |
head = git_reference__alloc_symbolic(GIT_HEAD_FILE, "refs/heads/master"); | |
postgres_refdb_backend__write(*backend_out, head, 1, NULL, NULL); | |
} | |
git_reference_free(head); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment