Current section

Files

Jump to
egit c_src git.cpp
Raw

c_src/git.cpp

#include <stdio.h>
#include <cstring>
#include <cassert>
#include <memory>
#include <tuple>
#include <vector>
#include <atomic>
#ifdef HAVE_FORMAT
#include <format>
#else
#include <fmt/format.h>
namespace std { using namespace fmt; }
#endif
#if !defined(GIT_REVSPEC_MERGE_BASE) && defined(GIT_REVPARSE_MERGE_BASE)
#define GIT_REVSPEC_MERGE_BASE GIT_REVPARSE_MERGE_BASE
#endif
#include <git2.h>
#ifndef GIT_OID_SHA1_HEXSIZE
#define GIT_OID_SHA1_HEXSIZE GIT_OID_HEXSZ
#endif
#include "git_utils.hpp"
#include "git_add.hpp"
#include "git_cat_file.hpp"
#include "git_checkout.hpp"
#include "git_commit.hpp"
#include "git_rev_parse.hpp"
#include "git_rev_list.hpp"
#include "git_config.hpp"
#include "git_branch.hpp"
#include "git_index.hpp"
#include "git_remote.hpp"
#include "git_tag.hpp"
#include "git_status.hpp"
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);
#ifdef NIF_DEBUG
fprintf(stderr, "=egit=> Freeing repo %p [%d]\r\n", p, __LINE__);
#endif
git_repository_free(p);
return enif_raise_exception(env, ATOM_ENOMEM);
}
auto result = enif_monitor_process(env, rp, &pid, &mon);
if (result != 0) [[unlikely]] {
#ifdef NIF_DEBUG
fprintf(stderr, "=egit=> Freeing repo %p (result=%d) [%d]\r\n", rp, result, __LINE__);
#endif
git_repository_free(p);
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);
}
}
return rp->to_enif_resource(env);
}
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 raise_badarg_exception(env, argv[1]);
std::string sha = bin_to_str(bsha);
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
SmartPtr<git_commit> commit(git_commit_free);
if (git_commit_lookup(&commit, repo->get(), &oid) < 0)
return raise_git_exception(env, "Failed to find git commit " + sha);
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 = bin_to_str(url);
std::string spath = bin_to_str(path);
git_repository* p{};
if (git_clone(&p, surl.c_str(), spath.c_str(), nullptr) < 0) [[unlikely]]
return raise_git_exception(env, "Failed to clone git repo " + surl);
#ifdef NIF_DEBUG
fprintf(stderr, "=egit=> Cloned repo %p [%d]\r\n", p, __LINE__);
#endif
return to_monitored_resource(env, p);
}
static ERL_NIF_TERM init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 2);
std::string path;
auto bare = false;
// Parse options
{
auto opts = argv[1];
ErlNifBinary bin;
if (!enif_inspect_binary(env, argv[0], &bin) || bin.size == 0) [[unlikely]]
return enif_make_badarg(env);
if (!enif_is_list(env, opts)) [[unlikely]]
return enif_make_badarg(env);
ERL_NIF_TERM opt;
while (enif_get_list_cell(env, opts, &opt, &opts)) {
if (enif_is_identical(opt, ATOM_BARE)) bare = true;
else [[unlikely]]
return raise_badarg_exception(env, opt);
}
path = bin_to_str(bin);
}
git_repository* p{};
if (git_repository_init(&p, path.c_str(), bare) != GIT_OK) [[unlikely]]
return raise_git_exception(env, std::format("Failed to init git repo {}", path));
#ifdef NIF_DEBUG
fprintf(stderr, "=egit=> Init repo %p [%d]\r\n", p, __LINE__);
#endif
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 = bin_to_str(path);
git_repository* p{};
if (git_repository_open(&p, spath.c_str()) < 0) [[unlikely]]
return raise_git_exception(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 = bin_to_str(bin);
}
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 message, "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);
if (!enif_is_list(env, argv[2])) [[unlikely]]
return enif_make_badarg(env);
std::string filename(bin_to_str(bin));
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 = bin_to_str(bin);
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 == 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_commit(env, repo->get(), bin_to_str(bin));
}
static ERL_NIF_TERM push_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 3);
// Parse options
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
std::string sremote = "origin";
if (!term_to_str(env, argv[1], sremote)) [[unlikely]]
return enif_make_badarg(env);
ERL_NIF_TERM ref, list = argv[2];
if (!enif_is_list(env, list)) [[unlikely]]
return raise_badarg_exception(env, list);
std::vector<std::string> ref_specs;
while (enif_get_list_cell(env, list, &ref, &list)) {
std::string str;
if (!term_to_str(env, ref, str)) [[unlikely]]
return raise_badarg_exception(env, ref);
ref_specs.push_back(str);
}
std::vector<const char*> cref_specs;
for (auto& s : ref_specs)
cref_specs.push_back(s.c_str());
git_strarray refspecs = {
.strings = const_cast<char**>(&cref_specs.front()),
.count = cref_specs.size()
};
SmartPtr<git_remote> remote(git_remote_free);
if (git_remote_lookup(&remote, repo->get(), sremote.c_str()) != GIT_OK) [[unlikely]]
return make_git_error(env, "Unable to lookup remote");
git_push_options options;
if (git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION) != GIT_OK) [[unlikely]]
return make_git_error(env, "Error initializing push");
return git_remote_push(remote, &refspecs, &options) == GIT_OK
? ATOM_OK : make_git_error(env, "Error pushing to " + sremote);
}
static ERL_NIF_TERM rev_parse_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);
return lg2_rev_parse(env, repo->get(), bin_to_str(bin), argv[2]);
}
static ERL_NIF_TERM rev_list_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])) [[unlikely]]
return enif_make_badarg(env);
if (!enif_is_list(env, argv[2])) [[unlikely]]
return enif_make_badarg(env);
return lg2_rev_list(env, repo->get(), argv[1], argv[2]);
}
static ERL_NIF_TERM config_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc >= 2);
if (argc > 3) [[unlikely]]
return enif_make_badarg(env);
return lg2_config(env, argv[0], argv[1], argc == 3 ? argv[2] : 0);
}
static ERL_NIF_TERM branch_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc >= 3 && argc <= 4);
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
ERL_NIF_TERM op = argv[1];
return lg2_branch(env, repo->get(), op, argv[2], argc == 4 ? argv[3] : 0);
}
static ERL_NIF_TERM list_branches_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);
return lg2_branch_list(env, repo->get(), argv[1]);
}
static ERL_NIF_TERM list_index_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);
if (!enif_is_list(env, argv[1])) [[unlikely]]
return enif_make_badarg(env);
return lg2_index(env, repo->get(), argv[1]);
}
static ERL_NIF_TERM remote_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 4);
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
ERL_NIF_TERM op = argv[1];
ErlNifBinary name;
if (!enif_inspect_binary(env, argv[2], &name) || name.size == 0) [[unlikely]]
return enif_make_badarg(env);
if (!(enif_is_tuple(env, op) || enif_is_atom(env, op)) || !enif_is_list(env, argv[3])) [[unlikely]]
return enif_make_badarg(env);
return lg2_remote(env, repo->get(), bin_to_str(name), op, argv[3]);
}
static ERL_NIF_TERM list_remotes_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);
return lg2_remotes_list(env, repo->get());
}
static ERL_NIF_TERM tag_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(argc == 4);
GitRepoPtr* repo;
if (!enif_get_resource(env, argv[0], GIT_REPO_RESOURCE, (void**)&repo)) [[unlikely]]
return enif_make_badarg(env);
ERL_NIF_TERM op = argv[1];
ErlNifBinary name;
if (!enif_inspect_binary(env, argv[2], &name)) [[unlikely]]
return enif_make_badarg(env);
if (!enif_is_atom(env, op) || !enif_is_list(env, argv[3])) [[unlikely]]
return enif_make_badarg(env);
return lg2_tag(env, repo->get(), bin_to_str(name), op, argv[3]);
}
static ERL_NIF_TERM status_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);
if (!enif_is_list(env, argv[1])) [[unlikely]]
return enif_make_badarg(env);
return lg2_status(env, repo->get(), argv[1]);
}
static ERL_NIF_TERM reset_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);
auto type = git_reset_t(0);
if (enif_is_identical(ATOM_SOFT, argv[1])) type = GIT_RESET_SOFT;
else if (enif_is_identical(ATOM_HARD, argv[1])) type = GIT_RESET_HARD;
else if (enif_is_identical(ATOM_MIXED, argv[1])) type = GIT_RESET_MIXED;
else [[unlikely]]
return enif_make_badarg(env);
std::string target;
if (!term_to_str(env, argv[2], target)) [[unlikely]]
return enif_make_badarg(env);
SmartPtr<git_object> id(git_object_free);
if (git_revparse_single(&id, repo->get(), target.c_str()) != GIT_OK) [[unlikely]]
return make_git_error(env, std::format("Failed to lookup commit {}", target));
return git_reset(repo->get(), id, type, nullptr) == GIT_OK
? ATOM_OK : make_git_error(env, "Cannot reset");
}
static void resource_dtor(ErlNifEnv* env, void* arg)
{
assert(arg);
#ifdef NIF_DEBUG
fprintf(stderr, "=egit=> Releasing resource %p [%d]\r\n", arg, __LINE__);
#endif
static_cast<GitRepoPtr*>(arg)->~GitRepoPtr();
}
static void resource_down(ErlNifEnv* env, void* obj, ErlNifPid*, ErlNifMonitor*)
{
#ifdef NIF_DEBUG
fprintf(stderr, "=egit=> Decremented resource ref %p [%d]\r\n", obj, __LINE__);
#endif
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 git_funcs[] =
{
{"init_nif", 2, init_nif},
{"clone_nif", 2, clone_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"open_nif", 1, open_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"fetch_nif", 2, fetch_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"fetch_nif", 3, fetch_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"add_nif", 3, add_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"checkout_nif", 3, checkout_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"push_nif", 3, push_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"commit_nif", 2, commit_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"commit_lookup_nif", 3, commit_lookup_nif, 0},
{"cat_file_nif", 3, cat_file_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"rev_parse_nif", 3, rev_parse_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"rev_list_nif", 3, rev_list_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"config_get_nif", 2, config_nif},
{"config_set_nif", 3, config_nif},
{"branch_nif", 3, branch_nif},
{"branch_nif", 4, branch_nif},
{"list_branches", 2, list_branches_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"list_index", 2, list_index_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"remote_nif", 4, remote_nif},
{"tag_nif", 4, tag_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"status_nif", 2, status_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"reset_nif", 3, reset_nif},
{"list_remotes", 1, list_remotes_nif},
};
ERL_NIF_INIT(git, git_funcs, load, NULL, upgrade, NULL);