Repositories / ocaml-git.git
src/ocaml_git_stubs.c
Clone (read-only): git clone http://git.guha-anderson.com/git/ocaml-git.git
#include <caml/alloc.h>
#include <caml/callback.h>
#include <caml/custom.h>
#include <caml/fail.h>
#include <caml/memory.h>
#include <caml/mlvalues.h>
#include <git2.h>
#include <stdint.h>
#include <string.h>
static void fail_git(const char *where, int code) {
const git_error *err = git_error_last();
char buffer[1024];
if (err && err->message) {
snprintf(buffer, sizeof(buffer), "%s: %s", where, err->message);
} else {
snprintf(buffer, sizeof(buffer), "%s failed with code %d", where, code);
}
caml_raise_with_string(*caml_named_value("ocaml_git_error"), buffer);
}
static void check(int code, const char *where) {
if (code < 0) fail_git(where, code);
}
static value copy_opt_string(const char *s) {
CAMLparam0();
CAMLlocal2(some, v);
if (!s) CAMLreturn(Val_int(0));
v = caml_copy_string(s);
some = caml_alloc(1, 0);
Store_field(some, 0, v);
CAMLreturn(some);
}
static value copy_oid(const git_oid *oid) {
char out[GIT_OID_HEXSZ + 1];
git_oid_tostr(out, sizeof(out), oid);
return caml_copy_string(out);
}
typedef struct {
git_repository *repo;
} repo_handle;
typedef struct {
git_index *index;
} index_handle;
static void finalize_repo(value v) {
repo_handle *h = Data_custom_val(v);
if (h->repo) {
git_repository_free(h->repo);
h->repo = NULL;
}
}
static void finalize_index(value v) {
index_handle *h = Data_custom_val(v);
if (h->index) {
git_index_free(h->index);
h->index = NULL;
}
}
static struct custom_operations repo_ops = {
"ocaml_git.repo",
finalize_repo,
custom_compare_default,
custom_hash_default,
custom_serialize_default,
custom_deserialize_default,
custom_compare_ext_default,
custom_fixed_length_default};
static struct custom_operations index_ops = {
"ocaml_git.index",
finalize_index,
custom_compare_default,
custom_hash_default,
custom_serialize_default,
custom_deserialize_default,
custom_compare_ext_default,
custom_fixed_length_default};
static git_repository *repo_val(value v) {
repo_handle *h = Data_custom_val(v);
if (!h->repo) caml_raise_with_string(*caml_named_value("ocaml_git_error"), "Repository is closed");
return h->repo;
}
static git_index *index_val(value v) {
index_handle *h = Data_custom_val(v);
if (!h->index) caml_raise_with_string(*caml_named_value("ocaml_git_error"), "Index is closed");
return h->index;
}
static value alloc_repo(git_repository *repo) {
CAMLparam0();
CAMLlocal1(v);
repo_handle *h;
v = caml_alloc_custom(&repo_ops, sizeof(repo_handle), 0, 1);
h = Data_custom_val(v);
h->repo = repo;
CAMLreturn(v);
}
static value alloc_index(git_index *index) {
CAMLparam0();
CAMLlocal1(v);
index_handle *h;
v = caml_alloc_custom(&index_ops, sizeof(index_handle), 0, 1);
h = Data_custom_val(v);
h->index = index;
CAMLreturn(v);
}
CAMLprim value kg_libgit2_init(value unit) {
CAMLparam1(unit);
git_libgit2_init();
CAMLreturn(Val_unit);
}
CAMLprim value kg_libgit2_shutdown(value unit) {
CAMLparam1(unit);
git_libgit2_shutdown();
CAMLreturn(Val_unit);
}
CAMLprim value kg_open(value path) {
CAMLparam1(path);
git_repository *repo = NULL;
int code = git_repository_open(&repo, String_val(path));
if (code < 0) code = git_repository_open_bare(&repo, String_val(path));
check(code, "git_repository_open");
CAMLreturn(alloc_repo(repo));
}
CAMLprim value kg_init(value path, value bare) {
CAMLparam2(path, bare);
git_repository *repo = NULL;
check(git_repository_init(&repo, String_val(path), Bool_val(bare)), "git_repository_init");
CAMLreturn(alloc_repo(repo));
}
CAMLprim value kg_discover(value start_path) {
CAMLparam1(start_path);
CAMLlocal1(result);
git_buf buf = GIT_BUF_INIT;
check(git_repository_discover(&buf, String_val(start_path), 0, NULL), "git_repository_discover");
result = caml_copy_string(buf.ptr ? buf.ptr : "");
git_buf_dispose(&buf);
CAMLreturn(result);
}
CAMLprim value kg_close(value repo_v) {
CAMLparam1(repo_v);
finalize_repo(repo_v);
CAMLreturn(Val_unit);
}
CAMLprim value kg_git_dir(value repo_v) {
CAMLparam1(repo_v);
const char *path = git_repository_path(repo_val(repo_v));
CAMLreturn(caml_copy_string(path ? path : ""));
}
CAMLprim value kg_workdir(value repo_v) {
CAMLparam1(repo_v);
CAMLreturn(copy_opt_string(git_repository_workdir(repo_val(repo_v))));
}
CAMLprim value kg_is_bare(value repo_v) {
CAMLparam1(repo_v);
CAMLreturn(Val_bool(git_repository_is_bare(repo_val(repo_v))));
}
CAMLprim value kg_is_empty(value repo_v) {
CAMLparam1(repo_v);
int empty = git_repository_is_empty(repo_val(repo_v));
check(empty, "git_repository_is_empty");
CAMLreturn(Val_bool(empty));
}
CAMLprim value kg_head_name(value repo_v) {
CAMLparam1(repo_v);
CAMLlocal1(result);
git_reference *ref = NULL;
check(git_repository_head(&ref, repo_val(repo_v)), "git_repository_head");
result = caml_copy_string(git_reference_name(ref));
git_reference_free(ref);
CAMLreturn(result);
}
static value copy_signature(const git_signature *sig) {
CAMLparam0();
CAMLlocal1(v);
v = caml_alloc_tuple(4);
Store_field(v, 0, caml_copy_string(sig->name ? sig->name : ""));
Store_field(v, 1, caml_copy_string(sig->email ? sig->email : ""));
Store_field(v, 2, Val_int((int)sig->when.time));
Store_field(v, 3, Val_int(sig->when.offset));
CAMLreturn(v);
}
static value copy_commit(git_commit *commit) {
CAMLparam0();
CAMLlocal4(v, author, committer, id);
v = caml_alloc_tuple(6);
id = copy_oid(git_commit_id(commit));
author = copy_signature(git_commit_author(commit));
committer = copy_signature(git_commit_committer(commit));
Store_field(v, 0, id);
Store_field(v, 1, caml_copy_string(git_commit_summary(commit) ? git_commit_summary(commit) : ""));
Store_field(v, 2, caml_copy_string(git_commit_message(commit) ? git_commit_message(commit) : ""));
Store_field(v, 3, author);
Store_field(v, 4, committer);
Store_field(v, 5, Val_int(git_commit_parentcount(commit)));
CAMLreturn(v);
}
static value lookup_commit_value(git_repository *repo, const git_oid *oid) {
CAMLparam0();
CAMLlocal1(v);
git_commit *commit = NULL;
check(git_commit_lookup(&commit, repo, oid), "git_commit_lookup");
v = copy_commit(commit);
git_commit_free(commit);
CAMLreturn(v);
}
CAMLprim value kg_head_commit(value repo_v) {
CAMLparam1(repo_v);
CAMLlocal1(result);
git_reference *ref = NULL;
const git_oid *oid;
check(git_repository_head(&ref, repo_val(repo_v)), "git_repository_head");
oid = git_reference_target(ref);
if (!oid) {
git_reference_free(ref);
caml_raise_with_string(*caml_named_value("ocaml_git_error"), "HEAD does not point to an object id");
}
result = lookup_commit_value(repo_val(repo_v), oid);
git_reference_free(ref);
CAMLreturn(result);
}
CAMLprim value kg_branch_commit(value repo_v, value branch_name) {
CAMLparam2(repo_v, branch_name);
CAMLlocal1(result);
git_reference *ref = NULL;
const git_oid *oid;
check(git_branch_lookup(&ref, repo_val(repo_v), String_val(branch_name), GIT_BRANCH_LOCAL), "git_branch_lookup");
oid = git_reference_target(ref);
if (!oid) {
git_reference_free(ref);
caml_raise_with_string(*caml_named_value("ocaml_git_error"), "Branch does not point to an object id");
}
result = lookup_commit_value(repo_val(repo_v), oid);
git_reference_free(ref);
CAMLreturn(result);
}
static value cons(value head, value tail) {
CAMLparam2(head, tail);
CAMLlocal1(cell);
cell = caml_alloc(2, 0);
Store_field(cell, 0, head);
Store_field(cell, 1, tail);
CAMLreturn(cell);
}
CAMLprim value kg_branches(value repo_v) {
CAMLparam1(repo_v);
CAMLlocal3(list, item, name_v);
git_branch_iterator *iter = NULL;
git_reference *ref = NULL;
git_branch_t type;
const char *name = NULL;
int code;
list = Val_emptylist;
check(git_branch_iterator_new(&iter, repo_val(repo_v), GIT_BRANCH_ALL), "git_branch_iterator_new");
while ((code = git_branch_next(&ref, &type, iter)) == 0) {
check(git_branch_name(&name, ref), "git_branch_name");
item = caml_alloc_tuple(3);
name_v = caml_copy_string(name ? name : "");
Store_field(item, 0, name_v);
Store_field(item, 1, Val_int(type == GIT_BRANCH_REMOTE ? 1 : 0));
Store_field(item, 2, Val_bool(git_branch_is_head(ref)));
list = cons(item, list);
git_reference_free(ref);
}
git_branch_iterator_free(iter);
if (code != GIT_ITEROVER) check(code, "git_branch_next");
CAMLreturn(list);
}
static int kind_tag(git_object_t type) {
switch (type) {
case GIT_OBJECT_BLOB: return 0;
case GIT_OBJECT_TREE: return 1;
case GIT_OBJECT_COMMIT: return 2;
case GIT_OBJECT_TAG: return 3;
default: return 4;
}
}
static value copy_tree_entry(const git_tree_entry *entry, const char *fallback_name) {
CAMLparam0();
CAMLlocal1(v);
const git_oid *oid = git_tree_entry_id(entry);
const char *name = git_tree_entry_name(entry);
v = caml_alloc_tuple(3);
Store_field(v, 0, caml_copy_string(name ? name : fallback_name));
Store_field(v, 1, copy_oid(oid));
Store_field(v, 2, Val_int(kind_tag(git_tree_entry_type(entry))));
CAMLreturn(v);
}
static void oid_from_string(git_oid *oid, value oid_v) {
check(git_oid_fromstr(oid, String_val(oid_v)), "git_oid_fromstr");
}
static git_commit *commit_from_oid_value(git_repository *repo, value oid_v) {
git_oid oid;
git_commit *commit = NULL;
oid_from_string(&oid, oid_v);
check(git_commit_lookup(&commit, repo, &oid), "git_commit_lookup");
return commit;
}
static git_tree *root_tree_from_commit(git_commit *commit) {
git_tree *tree = NULL;
check(git_commit_tree(&tree, commit), "git_commit_tree");
return tree;
}
CAMLprim value kg_tree_entry(value repo_v, value commit_oid_v, value path_v) {
CAMLparam3(repo_v, commit_oid_v, path_v);
CAMLlocal1(result);
git_commit *commit = commit_from_oid_value(repo_val(repo_v), commit_oid_v);
git_tree *tree = root_tree_from_commit(commit);
git_tree_entry *entry = NULL;
check(git_tree_entry_bypath(&entry, tree, String_val(path_v)), "git_tree_entry_bypath");
result = copy_tree_entry(entry, String_val(path_v));
git_tree_entry_free(entry);
git_tree_free(tree);
git_commit_free(commit);
CAMLreturn(result);
}
CAMLprim value kg_tree(value repo_v, value commit_oid_v, value path_v) {
CAMLparam3(repo_v, commit_oid_v, path_v);
CAMLlocal3(list, item, result);
git_commit *commit = commit_from_oid_value(repo_val(repo_v), commit_oid_v);
git_tree *root = root_tree_from_commit(commit);
git_tree *tree = root;
git_tree_entry *subentry = NULL;
size_t count, i;
list = Val_emptylist;
if (caml_string_length(path_v) > 0) {
check(git_tree_entry_bypath(&subentry, root, String_val(path_v)), "git_tree_entry_bypath");
if (git_tree_entry_type(subentry) != GIT_OBJECT_TREE) {
git_tree_entry_free(subentry);
git_tree_free(root);
git_commit_free(commit);
caml_raise_with_string(*caml_named_value("ocaml_git_error"), "path is not a tree");
}
check(git_tree_lookup(&tree, repo_val(repo_v), git_tree_entry_id(subentry)), "git_tree_lookup");
git_tree_entry_free(subentry);
}
count = git_tree_entrycount(tree);
for (i = count; i > 0; i--) {
const git_tree_entry *entry = git_tree_entry_byindex(tree, i - 1);
item = copy_tree_entry(entry, "");
list = cons(item, list);
}
result = list;
if (tree != root) git_tree_free(tree);
git_tree_free(root);
git_commit_free(commit);
CAMLreturn(result);
}
CAMLprim value kg_blob(value repo_v, value oid_v) {
CAMLparam2(repo_v, oid_v);
CAMLlocal2(result, bytes);
git_oid oid;
git_blob *blob = NULL;
const void *content;
git_object_size_t size;
oid_from_string(&oid, oid_v);
check(git_blob_lookup(&blob, repo_val(repo_v), &oid), "git_blob_lookup");
size = git_blob_rawsize(blob);
content = git_blob_rawcontent(blob);
bytes = caml_alloc_string((mlsize_t)size);
if (size > 0 && content) memcpy(Bytes_val(bytes), content, (size_t)size);
result = bytes;
git_blob_free(blob);
CAMLreturn(result);
}
CAMLprim value kg_commit_lookup(value repo_v, value oid_v) {
CAMLparam2(repo_v, oid_v);
CAMLlocal1(result);
git_oid oid;
git_commit *commit = NULL;
oid_from_string(&oid, oid_v);
check(git_commit_lookup(&commit, repo_val(repo_v), &oid), "git_commit_lookup");
result = copy_commit(commit);
git_commit_free(commit);
CAMLreturn(result);
}
/* Unified-diff text for a commit against its first parent (or the empty tree
for a root commit). Mirrors `git show --format= <id>` output. */
CAMLprim value kg_commit_diff(value repo_v, value oid_v) {
CAMLparam2(repo_v, oid_v);
CAMLlocal1(result);
git_repository *repo = repo_val(repo_v);
git_oid oid;
git_commit *commit = NULL;
git_commit *parent = NULL;
git_tree *new_tree = NULL;
git_tree *old_tree = NULL;
git_diff *diff = NULL;
git_diff_options opts;
git_buf buf = GIT_BUF_INIT;
oid_from_string(&oid, oid_v);
check(git_commit_lookup(&commit, repo, &oid), "git_commit_lookup");
check(git_commit_tree(&new_tree, commit), "git_commit_tree");
if (git_commit_parentcount(commit) > 0) {
check(git_commit_parent(&parent, commit, 0), "git_commit_parent");
check(git_commit_tree(&old_tree, parent), "git_commit_tree");
}
check(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION), "git_diff_options_init");
check(git_diff_tree_to_tree(&diff, repo, old_tree, new_tree, &opts), "git_diff_tree_to_tree");
check(git_diff_to_buf(&buf, diff, GIT_DIFF_FORMAT_PATCH), "git_diff_to_buf");
result = caml_copy_string(buf.ptr ? buf.ptr : "");
git_buf_dispose(&buf);
git_diff_free(diff);
if (old_tree) git_tree_free(old_tree);
git_tree_free(new_tree);
if (parent) git_commit_free(parent);
git_commit_free(commit);
CAMLreturn(result);
}
CAMLprim value kg_commits(value repo_v, value branch_name_v, value limit_v) {
CAMLparam3(repo_v, branch_name_v, limit_v);
CAMLlocal3(list, commit_v, result);
git_reference *ref = NULL;
const git_oid *start_oid;
git_revwalk *walker = NULL;
git_oid oid;
int limit = Int_val(limit_v);
int count = 0;
list = Val_emptylist;
check(git_branch_lookup(&ref, repo_val(repo_v), String_val(branch_name_v), GIT_BRANCH_LOCAL), "git_branch_lookup");
start_oid = git_reference_target(ref);
check(git_revwalk_new(&walker, repo_val(repo_v)), "git_revwalk_new");
git_revwalk_sorting(walker, GIT_SORT_TIME);
check(git_revwalk_push(walker, start_oid), "git_revwalk_push");
while (count < limit && git_revwalk_next(&oid, walker) == 0) {
commit_v = lookup_commit_value(repo_val(repo_v), &oid);
list = cons(commit_v, list);
count++;
}
git_revwalk_free(walker);
git_reference_free(ref);
result = Val_emptylist;
while (list != Val_emptylist) {
result = cons(Field(list, 0), result);
list = Field(list, 1);
}
CAMLreturn(result);
}
static value flags_from_status(unsigned int status) {
CAMLparam0();
CAMLlocal1(list);
list = Val_emptylist;
if (status == GIT_STATUS_CURRENT) list = cons(Val_int(0), list);
if (status & GIT_STATUS_INDEX_NEW) list = cons(Val_int(1), list);
if (status & GIT_STATUS_INDEX_MODIFIED) list = cons(Val_int(2), list);
if (status & GIT_STATUS_INDEX_DELETED) list = cons(Val_int(3), list);
if (status & GIT_STATUS_INDEX_RENAMED) list = cons(Val_int(4), list);
if (status & GIT_STATUS_INDEX_TYPECHANGE) list = cons(Val_int(5), list);
if (status & GIT_STATUS_WT_NEW) list = cons(Val_int(6), list);
if (status & GIT_STATUS_WT_MODIFIED) list = cons(Val_int(7), list);
if (status & GIT_STATUS_WT_DELETED) list = cons(Val_int(8), list);
if (status & GIT_STATUS_WT_TYPECHANGE) list = cons(Val_int(9), list);
if (status & GIT_STATUS_WT_RENAMED) list = cons(Val_int(10), list);
if (status & GIT_STATUS_WT_UNREADABLE) list = cons(Val_int(11), list);
if (status & GIT_STATUS_IGNORED) list = cons(Val_int(12), list);
if (status & GIT_STATUS_CONFLICTED) list = cons(Val_int(13), list);
CAMLreturn(list);
}
CAMLprim value kg_status(value repo_v) {
CAMLparam1(repo_v);
CAMLlocal4(list, item, path_v, flags);
git_status_options opts;
git_status_list *statuses = NULL;
size_t count, i;
check(git_status_options_init(&opts, GIT_STATUS_OPTIONS_VERSION), "git_status_options_init");
opts.flags = GIT_STATUS_OPT_DEFAULTS;
check(git_status_list_new(&statuses, repo_val(repo_v), &opts), "git_status_list_new");
count = git_status_list_entrycount(statuses);
list = Val_emptylist;
for (i = count; i > 0; i--) {
const git_status_entry *entry = git_status_byindex(statuses, i - 1);
const git_diff_delta *delta = entry->index_to_workdir ? entry->index_to_workdir : entry->head_to_index;
const char *path = "";
if (delta) path = delta->new_file.path ? delta->new_file.path : delta->old_file.path;
item = caml_alloc_tuple(2);
path_v = caml_copy_string(path ? path : "");
flags = flags_from_status(entry->status);
Store_field(item, 0, path_v);
Store_field(item, 1, flags);
list = cons(item, list);
}
git_status_list_free(statuses);
CAMLreturn(list);
}
CAMLprim value kg_index(value repo_v) {
CAMLparam1(repo_v);
git_index *index = NULL;
check(git_repository_index(&index, repo_val(repo_v)), "git_repository_index");
CAMLreturn(alloc_index(index));
}
CAMLprim value kg_index_size(value index_v) {
CAMLparam1(index_v);
CAMLreturn(Val_int(git_index_entrycount(index_val(index_v))));
}
CAMLprim value kg_index_contains(value index_v, value path_v) {
CAMLparam2(index_v, path_v);
CAMLreturn(Val_bool(git_index_get_bypath(index_val(index_v), String_val(path_v), 0) != NULL));
}
CAMLprim value kg_index_add(value index_v, value path_v) {
CAMLparam2(index_v, path_v);
check(git_index_add_bypath(index_val(index_v), String_val(path_v)), "git_index_add_bypath");
CAMLreturn(Val_unit);
}
CAMLprim value kg_index_remove(value index_v, value path_v) {
CAMLparam2(index_v, path_v);
check(git_index_remove_bypath(index_val(index_v), String_val(path_v)), "git_index_remove_bypath");
CAMLreturn(Val_unit);
}
CAMLprim value kg_index_write(value index_v) {
CAMLparam1(index_v);
check(git_index_write(index_val(index_v)), "git_index_write");
CAMLreturn(Val_unit);
}
CAMLprim value kg_index_close(value index_v) {
CAMLparam1(index_v);
finalize_index(index_v);
CAMLreturn(Val_unit);
}