Current section
Files
Jump to
Current section
Files
c_src/egit.cpp
#include <stdio.h>
#include <cstring>
#include <cassert>
#include <memory>
#include <tuple>
#include <vector>
#include <atomic>
#ifndef USE_FMT_LIB
#include <format>
#else
#include <fmt/format.h>
namespace std { using namespace fmt; }
#endif
#ifndef GIT_OID_SHA1_HEXSIZE
#define GIT_OID_SHA1_HEXSIZE GIT_OID_HEXSZ
#endif
#include <git2.h>
#include "egit_utils.hpp"
#include "egit_add.hpp"
#include "egit_cat_file.hpp"
#include "egit_checkout.hpp"
#include "egit_commit.hpp"
#include "egit_rev_parse.hpp"
static ErlNifResourceType* GIT_REPO_RESOURCE;
struct GitRepoPtr {
static GitRepoPtr* create(git_repository* p) {
auto rp = static_cast<GitRepoPtr*>(enif_alloc_resource(GIT_REPO_RESOURCE, sizeof(GitRepoPtr)));
if (!rp) [[unlikely]]
return nullptr;
new (rp) GitRepoPtr(p);
return rp;
}
~GitRepoPtr() { if (m_ptr) { git_repository_free(m_ptr); m_ptr = nullptr; } }
git_repository const* get() const { return m_ptr; }
git_repository* get() { return m_ptr; }
private:
GitRepoPtr(git_repository* p) : m_ptr(p) {}
git_repository* m_ptr;
};
struct GitCommit {
explicit GitCommit(git_commit* p = nullptr) : m_ptr(p) {}
~GitCommit() {
if (m_ptr) {
git_commit_free(m_ptr);
m_ptr = nullptr;
}
}
git_commit const* get() const { return m_ptr; }
git_commit* get() { return m_ptr; }
git_commit** ptr() { assert(!m_ptr); return &m_ptr; }
git_commit const* operator->() const { return m_ptr; }
private:
git_commit* m_ptr;
};
static ERL_NIF_TERM to_monitored_resource(ErlNifEnv* env, git_repository* p)
{
ErlNifMonitor mon;
ErlNifPid pid;
enif_self(env, &pid);
auto rp = GitRepoPtr::create(p);
if (!rp) [[unlikely]] {
assert(p);
git_repository_free(p);
return enif_raise_exception(env, ATOM_ENOMEM);
}
auto result = enif_monitor_process(env, rp, &pid, &mon);
if (result != 0) [[unlikely]] {
if (result > 0) {
// Process no longer alive
return enif_raise_exception(env, ATOM_ENOPROCESS);
} else {
assert(result < 0);
// mon callback is not specified
return enif_raise_exception(env, ATOM_ENOCALLBACK);
}
}
ERL_NIF_TERM resource = enif_make_resource(env, (void*)rp);
enif_release_resource((void*)rp); // Grant ownership to the resource
return resource;
}
static ERL_NIF_TERM oid_to_bin(ErlNifEnv* env, git_oid const* oid, size_t len = GIT_OID_SHA1_HEXSIZE)
{
char buf[GIT_OID_SHA1_HEXSIZE+1];
len = std::min(len, sizeof(buf)-1);
git_oid_tostr(buf, len, oid);
return make_binary(env, buf);
}
static ERL_NIF_TERM
commit_lookup_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 3);
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
ErlNifBinary bsha;
if (!enif_inspect_binary(env, argv[1], &bsha)) [[unlikely]]
return enif_make_badarg(env);
std::string sha((char*)bsha.data, bsha.size);
git_oid oid;
if (git_oid_fromstr(&oid, sha.c_str()) < 0)
return ATOM_NIL;
std::vector<ERL_NIF_TERM> keys, vals;
auto push = [&keys, &vals, env](ERL_NIF_TERM key, const char* val) {
keys.push_back(key);
vals.push_back(val ? make_binary(env, val) : ATOM_NIL);
};
auto pushi = [&keys, &vals, env](ERL_NIF_TERM key, int64_t val) {
keys.push_back(key);
vals.push_back(enif_make_int64(env, val));
};
auto pusht = [&keys, &vals](ERL_NIF_TERM key, ERL_NIF_TERM val) {
keys.push_back(key);
vals.push_back(val);
};
auto push_sign = [&, env](ERL_NIF_TERM key, git_signature const* val) {
keys.push_back(key);
vals.push_back(enif_make_tuple2(env, make_binary(env, val->name), make_binary(env, val->email)));
};
// Smart pointer that will automatically free the commit object
GitCommit pcommit;
if (git_commit_lookup(pcommit.ptr(), repo->get(), &oid) < 0)
return raise_git_error(env, "Failed to find git commit " + sha);
auto commit = pcommit.get();
ERL_NIF_TERM head, list = argv[2];
while (enif_get_list_cell(env, list, &head, &list)) {
if (enif_is_identical(head, ATOM_ENCODING)) push(ATOM_ENCODING, git_commit_message_encoding(commit));
else if (enif_is_identical(head, ATOM_MESSAGE)) push(ATOM_MESSAGE, git_commit_message (commit));
else if (enif_is_identical(head, ATOM_SUMMARY)) push(ATOM_SUMMARY, git_commit_summary (commit));
else if (enif_is_identical(head, ATOM_TIME)) pushi(ATOM_TIME, git_commit_time (commit));
else if (enif_is_identical(head, ATOM_TIME_OFFSET)) pushi(ATOM_TIME_OFFSET, git_commit_time_offset (commit) * 60L);
else if (enif_is_identical(head, ATOM_COMMITTER)) push_sign(ATOM_COMMITTER, git_commit_committer (commit));
else if (enif_is_identical(head, ATOM_AUTHOR)) push_sign(ATOM_AUTHOR, git_commit_author (commit));
else if (enif_is_identical(head, ATOM_HEADER)) push(ATOM_HEADER, git_commit_raw_header (commit));
else if (enif_is_identical(head, ATOM_TREE_ID)) pusht(ATOM_TREE_ID, oid_to_bin(env, git_commit_tree_id(commit)));
else [[unlikely]]
return enif_make_badarg(env);
}
ERL_NIF_TERM map;
if (!enif_make_map_from_arrays(env, &keys.front(), &vals.front(), keys.size(), &map)) [[unlikely]]
return enif_raise_exception(env, ATOM_ENOMEM);
return map;
}
static ERL_NIF_TERM clone_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary url, path;
assert(argc == 2);
if (!enif_inspect_binary(env, argv[0], &url) ||
!enif_inspect_binary(env, argv[1], &path)) [[unlikely]]
return enif_make_badarg(env);
std::string surl((char*)url.data, url.size);
std::string spath((char*)path.data, path.size);
git_repository* p{};
if (git_clone(&p, surl.c_str(), spath.c_str(), nullptr) < 0) [[unlikely]]
return raise_git_error(env, "Failed to clone git repo " + surl);
return to_monitored_resource(env, p);
}
static ERL_NIF_TERM open_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary path;
assert(argc == 1);
if (!enif_inspect_binary(env, argv[0], &path)) [[unlikely]]
return enif_make_badarg(env);
std::string spath((char*)path.data, path.size);
git_repository* p{};
if (git_repository_open(&p, spath.c_str()) < 0) [[unlikely]]
return raise_git_error(env, "Failed to open git repo " + spath);
return to_monitored_resource(env, p);
}
static ERL_NIF_TERM fetch_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
const char* fetch_or_pull = nullptr;
if (enif_is_identical(argv[1], ATOM_FETCH))
fetch_or_pull = "fetch";
else if (enif_is_identical(argv[1], ATOM_PULL))
fetch_or_pull = "pull";
else
return enif_make_badarg(env);
std::string remote_name("origin");
if (argc > 2) {
ErlNifBinary bin;
if (!enif_inspect_binary(env, argv[2], &bin)) [[unlikely]]
return enif_make_badarg(env);
remote_name = std::string((char*)bin.data, bin.size);
}
git_remote* remote;
if (git_remote_lookup(&remote, repo->get(), remote_name.c_str()) < 0)
return make_git_error(env, "Failed to lookup remote " + remote_name);
if (git_remote_fetch(remote,
NULL, // refspecs, NULL to use the configured ones
NULL, // options, empty for defaults
fetch_or_pull) < 0) // reflog mesage, "fetch" (or NULL) or "pull"
return make_git_error(env, "Failed to fetch from " + remote_name);
return ATOM_OK;
}
static ERL_NIF_TERM cat_file_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 3);
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
ErlNifBinary bin;
if (!enif_inspect_binary(env, argv[1], &bin) || bin.size == 0) [[unlikely]]
return enif_make_badarg(env);
std::string filename((char*)bin.data, bin.size);
return lg2_cat_file(env, repo->get(), filename, argv[2]);
}
static ERL_NIF_TERM checkout_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 3);
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
ErlNifBinary bin;
if (!enif_inspect_binary(env, argv[1], &bin) || bin.size == 0) [[unlikely]]
return enif_make_badarg(env);
if (!enif_is_list(env, argv[2])) [[unlikely]]
return enif_make_badarg(env);
std::string rev((char*)bin.data, bin.size);
return lg2_checkout(env, repo->get(), rev, argv[2]);
}
static ERL_NIF_TERM add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 3);
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
if (!enif_is_list(env, argv[1]) || !enif_is_list(env, argv[2])) [[unlikely]]
return enif_make_badarg(env);
return lg2_add(env, repo->get(), argv[1], argv[2]);
}
static ERL_NIF_TERM commit_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 1);
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
ErlNifBinary bin;
if (!enif_inspect_binary(env, argv[1], &bin) || bin.size == 0) [[unlikely]]
return enif_make_badarg(env);
return lg2_commit(env, repo->get(), std::string((const char*)bin.data, bin.size));
}
static ERL_NIF_TERM rev_parse_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 2);
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
ErlNifBinary bin;
if (!enif_inspect_binary(env, argv[1], &bin) || bin.size == 0) [[unlikely]]
return enif_make_badarg(env);
return lg2_rev_parse(env, repo->get(), std::string((const char*)bin.data, bin.size));
}
static void resource_dtor(ErlNifEnv* env, void* arg)
{
assert(arg);
//fprintf(stderr, "--> Releasing resource %p\r\n", arg);
static_cast<GitRepoPtr*>(arg)->~GitRepoPtr();
}
static void resource_down(ErlNifEnv* env, void* obj, ErlNifPid*, ErlNifMonitor*)
{
//fprintf(stderr, "--> Decrement resource ref %p\r\n", obj);
enif_release_resource(obj);
}
static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
{
init_atoms(env);
auto flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
ErlNifResourceTypeInit rti = {.dtor = &resource_dtor, .down = &resource_down};
GIT_REPO_RESOURCE = enif_open_resource_type_x(env, "git_repo_resource", &rti, flags, nullptr);
git_libgit2_init();
return 0;
}
static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) {
//if (old_priv_data)
// enif_release_resource(old_priv_data);
return 0;
}
static ErlNifFunc egit_funcs[] =
{
{"clone", 2, clone_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"open", 1, open_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"fetch_or_pull", 2, fetch_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"fetch_or_pull", 3, fetch_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"add_nif", 3, add_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"checkout", 3, checkout_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"commit", 2, commit_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"cat_file", 3, cat_file_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"rev_parse", 2, rev_parse_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"commit_lookup", 3, commit_lookup_nif, 0},
};
ERL_NIF_INIT(egit, egit_funcs, load, NULL, upgrade, NULL);