Repositories / ocaml-git.git

src/ocaml_git_stubs.c

Clone (read-only): git clone http://git.guha-anderson.com/git/ocaml-git.git

Branch
18390 bytes · ffcefcb47a51
#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); }