diff --git a/c_src/commit.c b/c_src/commit.c index faf0a33..318f2e5 100644 --- a/c_src/commit.c +++ b/c_src/commit.c @@ -2,8 +2,10 @@ #include "repository.h" #include "object.h" #include "oid.h" +#include "signature.h" #include #include +#include ERL_NIF_TERM geef_commit_tree_id(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) @@ -43,3 +45,92 @@ geef_commit_tree(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_tuple3(env, atoms.ok, atoms.tree, term_obj); } + +ERL_NIF_TERM +geef_commit_create(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + geef_repository *repo; + ErlNifBinary bin; + char *ref = NULL, *encoding = NULL, *message = NULL; + git_signature *author = NULL, *committer = NULL; + ERL_NIF_TERM err, head, tail; + unsigned int parents_len, i; + git_oid tree, *parents_ids, commit_id; + const git_oid **parents_ids_ptrs; + + if (!enif_get_resource(env, argv[0], geef_repository_type, (void **) &repo)) + return enif_make_badarg(env); + + if (enif_compare(argv[1], atoms.undefined)) { + if (!enif_inspect_iolist_as_binary(env, argv[1], &bin)) + return enif_make_badarg(env); + ref = strndup((char *)bin.data, bin.size); + if (ref == NULL) + return geef_oom(env); + } + + if (geef_signature_from_erl(&author, env, &err, argv[2]) < 0) + return err; + + if (geef_signature_from_erl(&committer, env, &err, argv[3]) < 0) { + git_signature_free(author); + return err; + } + + if (enif_compare(argv[4], atoms.undefined)) { + if (!enif_inspect_iolist_as_binary(env, argv[4], &bin)) + return enif_make_badarg(env); + encoding = strndup((char *)bin.data, bin.size); + if (encoding == NULL) + return geef_oom(env); + } + + if (!enif_inspect_iolist_as_binary(env, argv[5], &bin)) + return enif_make_badarg(env); + + message = strndup((char *)bin.data, bin.size); + if (message == NULL) + return geef_oom(env); + + if (!enif_inspect_binary(env, argv[6], &bin)) + return enif_make_badarg(env); + if (bin.size != GIT_OID_RAWSZ) + return enif_make_badarg(env); + + git_oid_fromraw(&tree, bin.data); + + if (!enif_get_list_length(env, argv[7], &parents_len)) + return enif_make_badarg(env); + + parents_ids = calloc(parents_len, sizeof(git_oid)); + if (parents_ids == NULL) + return geef_oom(env); + + parents_ids_ptrs = calloc(parents_len, sizeof(git_oid *)); + if (parents_ids_ptrs == NULL) + return geef_oom(env); + + i = 0; + tail = argv[8]; + while (enif_get_list_cell(env, tail, &head, &tail)) { + if (!enif_inspect_binary(env, head, &bin)) + return enif_make_badarg(env); + if (bin.size != GIT_OID_RAWSZ) + return enif_make_badarg(env); + + git_oid_fromraw(&parents_ids[i], bin.data); + parents_ids_ptrs[i] = &parents_ids[i]; + i++; + } + + if (git_commit_create_from_oids(&commit_id, repo->repo, ref, author, committer, encoding, message, + &tree, parents_len, parents_ids_ptrs) < 0) + return geef_error(env); + + if (!enif_realloc_binary(&bin, GIT_OID_RAWSZ)) + return geef_oom(env); + + memcpy(bin.data, &commit_id, GIT_OID_RAWSZ); + + return enif_make_tuple2(env, atoms.ok, enif_make_binary(env, &bin)); +} diff --git a/c_src/commit.h b/c_src/commit.h index 3699c05..e0acd03 100644 --- a/c_src/commit.h +++ b/c_src/commit.h @@ -3,5 +3,5 @@ ERL_NIF_TERM geef_commit_tree_id(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM geef_commit_tree(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - +ERL_NIF_TERM geef_commit_create(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); #endif diff --git a/c_src/geef.c b/c_src/geef.c index ee4a9b7..bee4dad 100644 --- a/c_src/geef.c +++ b/c_src/geef.c @@ -163,6 +163,7 @@ static ErlNifFunc geef_funcs[] = {"object_id", 1, geef_object_id}, {"commit_tree", 1, geef_commit_tree}, {"commit_tree_id", 1, geef_commit_tree_id}, + {"commit_create", 8, geef_commit_create}, {"tree_bypath", 2, geef_tree_bypath}, {"tree_nth", 2, geef_tree_nth}, {"tree_count", 1, geef_tree_count}, diff --git a/c_src/signature.c b/c_src/signature.c index 2ce8863..5719f39 100644 --- a/c_src/signature.c +++ b/c_src/signature.c @@ -3,6 +3,93 @@ #include #include +int geef_signature_from_erl(git_signature **out, ErlNifEnv *env, ERL_NIF_TERM *err, ERL_NIF_TERM term) +{ + const ERL_NIF_TERM *time_tuple, *time, *tuple; + ErlNifBinary name, email; + git_signature *sig; + git_time_t gtime; + int offset, arity; + unsigned int secs, megasecs; + + memset(&name, 0, sizeof(ErlNifBinary)); + memset(&email, 0, sizeof(ErlNifBinary)); + + if (!enif_get_tuple(env, term, &arity, &tuple)) + goto on_badarg; + + if (arity != 4) + goto on_badarg; + + if (!enif_inspect_iolist_as_binary(env, tuple[1], &name)) + goto on_badarg; + + if (!enif_inspect_iolist_as_binary(env, tuple[2], &email)) + goto on_badarg; + + if (!geef_terminate_binary(&name)) + goto on_oom; + + if (!geef_terminate_binary(&email)) + goto on_oom; + + /* + * Now that we have the name and e-mail, we need to extract + * the time tuple. This is quite annoying as the time is yet + * another tuple. + */ + if (!enif_get_tuple(env, tuple[3], &arity, &time_tuple)) + goto on_badarg; + + if (arity != 2) + goto on_badarg; + + /* + * The first element of the time is an erlang timestamp, which + * separates megasecs out, for whatever reason + */ + if (!enif_get_tuple(env, time_tuple[0], &arity, &time)) + goto on_badarg; + + if (arity != 3) + goto on_badarg; + + if (!enif_get_uint(env, time[0], &megasecs)) + goto on_badarg; + if (!enif_get_uint(env, time[1], &secs)) + goto on_badarg; + + if (!enif_get_int(env, time_tuple[1], &offset)) + goto on_badarg; + + gtime = megasecs * 1000000 + secs; + + /* Finally we have all the data */ + + if (git_signature_new(&sig, (char *)name.data, (char *)email.data, gtime, offset) < 0) { + enif_release_binary(&name); + enif_release_binary(&email); + *err = geef_error(env); + return -1; + } + + *out = sig; + return 0; + +on_badarg: + enif_release_binary(&name); + enif_release_binary(&email); + *err = enif_make_badarg(env); + return -1; + +on_oom: + enif_release_binary(&name); + enif_release_binary(&email); + *err = geef_oom(env); + return -1; + +} + static int geef_string_to_bin(ErlNifBinary *bin, const char *str) { size_t len; diff --git a/c_src/signature.h b/c_src/signature.h index 2c63971..b3e1e97 100644 --- a/c_src/signature.h +++ b/c_src/signature.h @@ -1,4 +1,10 @@ #include "geef.h" +#ifndef GEEF_SIGNATURE_H +#define GEEF_SIGNATURE_H + ERL_NIF_TERM geef_signature_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM geef_signature_default(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +int geef_signature_from_erl(git_signature **out, ErlNifEnv *env, ERL_NIF_TERM *err, ERL_NIF_TERM term); + +#endif diff --git a/lib/geef/commit.ex b/lib/geef/commit.ex index fafab41..e04fbf6 100644 --- a/lib/geef/commit.ex +++ b/lib/geef/commit.ex @@ -1,5 +1,7 @@ defmodule Geef.Commit do alias Geef.Object + alias Geef.Signature + alias Geef.Oid import Object, only: :macros @type t :: Object[type: :commit] @@ -31,4 +33,9 @@ defmodule Geef.Commit do @spec tree!(t) :: Tree.t def tree!(commit = Object[type: :commit]), do: tree(commit) |> Geef.assert_ok + @type create(pid, Signature.t, Signature.t, iolist, Tree.t, Commit.t, [atom]) :: {:ok, Oid.t} | {:error, term} + def create(repo, author = Signature[], committer = Signature[], message, tree, parents, opts // []) do + :geef_commit.create(repo, author, committer, message, tree, parents, opts) + end + end diff --git a/lib/geef/tree.ex b/lib/geef/tree.ex index fb29988..ddb100d 100644 --- a/lib/geef/tree.ex +++ b/lib/geef/tree.ex @@ -11,6 +11,8 @@ defrecord Geef.Tree, Record.extract(:geef_object, from: "src/geef_records.hrl") import Object, only: :macros + @type t :: Object[type: :commit] + def lookup(repo, id) do case :geef_tree.lookup(repo, id) do {:ok, obj} -> diff --git a/src/geef_commit.erl b/src/geef_commit.erl index 7acaef1..3720c19 100644 --- a/src/geef_commit.erl +++ b/src/geef_commit.erl @@ -1,5 +1,6 @@ -module(geef_commit). -export([tree_id/1, tree/1, lookup/2]). +-export([create/5, create/6, create/7]). -include("geef_records.hrl"). @@ -22,3 +23,29 @@ tree(#geef_object{type=commit,handle=Handle}) -> -spec lookup(pid(), geef_oid:oid()) -> {ok, commit()} | {error, term()}. lookup(Repo, Id) -> geef_obj:lookup(Repo, Id, commit). + + +%% Full version, accepts all paremeters +-spec create(pid(), iolist(), geef_sig:signature(), geef_sig:signature(), + iolist(), iolist(), geef_oid:oid(), [geef_oid:oid()]) -> {ok, geef_oid:oid()} | {error, term()}. +create(Repo, Ref, Author = #geef_signature{}, Committer = #geef_signature{}, Encoding, Message, Tree, Parents) + when is_list(Parents) -> + Handle = geef_repo:handle(Repo), + geef_nif:commit_create(Handle, Ref, Author, Committer, Encoding, Message, Tree, Parents). + +% Common version, accepts ref and encoding as options +create(Repo, Author = #geef_signature{}, Committer = #geef_signature{}, Message, Tree, Parents, Opts) -> + Ref = proplists:get_value(update_ref, Opts, undefined), + Encoding = proplists:get_value(encoding, Opts, undefined), + create(Repo, Ref, Author, Committer, Encoding, Message, Tree, Parents). + +create(Repo, Author = #geef_signature{}, Committer = #geef_signature{}, Message, Tree, Parents) -> + create(Repo, Author, Committer, Message, Tree, Parents, []); + +% Version with both the same +%% @doc Create a new commit. Person will be used for both author and commiter. +create(Repo, Person = #geef_signature{}, Message, Tree, Parents, Opts) -> + create(Repo, Person, Person, Message, Tree, Parents, Opts). + +create(Repo, Person = #geef_signature{}, Message, Tree, Parents) -> + create(Repo, Person, Person, Message, Tree, Parents, []). diff --git a/src/geef_nif.erl b/src/geef_nif.erl index b638932..8f90644 100644 --- a/src/geef_nif.erl +++ b/src/geef_nif.erl @@ -96,6 +96,9 @@ commit_tree_id(_Handle) -> commit_tree(_Handle) -> nif_error(?LINE). +commit_create(_RepoHandle, _Ref, _Author, _Committer, _Encoding, _Message, _Tree, _Parents) -> + ?NIF_FN. + -spec tree_bypath(term, iolist()) -> term(). tree_bypath(_TreeHandle, _Path) -> nif_error(?LINE). diff --git a/test/repo_test.erl b/test/repo_test.erl index 5b4e3ed..54c8d4e 100644 --- a/test/repo_test.erl +++ b/test/repo_test.erl @@ -6,7 +6,8 @@ repo_test_() -> {foreach, fun start/0, fun stop/1, [fun bare_test/1, fun odb_write_test/1, fun ref_test/1, fun index_add_test/1, - fun ref_iter_test/1, fun revparse_test/1]}. + fun ref_iter_test/1, fun revparse_test/1, + fun commit_create_test/1]}. start() -> {A, B, C} = now(), @@ -82,6 +83,16 @@ ref_iter_test(Repo) -> ?_assertEqual(Ref2#geef_reference.name, <<"refs/heads/branch">>), ?_assertEqual(Res1, {error, iterover})]. +commit_create_test(Repo) -> + odb_write_test(Repo), + index_add_test(Repo), + TreeId = geef_oid:parse("5a20bbbf65ea75ad4d9f995d179156824ccca3a1"), + CommitId = geef_oid:parse("bf968373f95f8fed2a24f9d25ebf06521359c6bc"), + Sig = #geef_signature{name= <<"foo">>, email= <<"bar">>, time={{1381,949139,0}, 120}}, + Message = <<"Commit message">>, + Resp = geef_commit:create(Repo, Sig, Message, TreeId, []), + [?_assertMatch({ok, CommitId}, Resp)]. + rm_r(Path) -> case filelib:is_dir(Path) of false ->