/* git2.c * * Simple binding to the parts of libgit2 in use by Gall. * * Copyright 2014 Daniel Silverstone */ /** * Low-Level C binding for Gall's use of libgit2. * * @module gall.ll.git2 */ #include #include #include "git2.h" typedef struct { git_repository *repo; git_odb *odb; } repo_et_al_t; static inline git_repository *to_repo(lua_State *L, int idx) { repo_et_al_t *real = lua_touserdata(L, idx); if (real == NULL) return NULL; return real->repo; } static inline git_odb *to_odb(lua_State *L, int idx) { repo_et_al_t *real = lua_touserdata(L, idx); if (real == NULL) return NULL; return real->odb; } /* Push the most recent error from libgit2 */ static int push_git2_error(lua_State *L, int retcode) { const git_error *err = giterr_last(); lua_pushnil(L); if (err == NULL) { lua_pushfstring(L, "Unknown error: %d", retcode); } else { lua_pushstring(L, err->message); } return 2; } static int L_gc_repo(lua_State *L) { repo_et_al_t *real = lua_touserdata(L, 1); if (real->odb != NULL) git_odb_free(real->odb); if (real->repo != NULL) git_repository_free(real->repo); real->repo = NULL; real->odb = NULL; return 0; } /** * Open a repository from disk. * * This function opens a repository on disk using libgit2. This low level * repository type can be used in routines such as @{lookup_symbolic_ref} or * @{lookup_sha_from_ref}. * * @function open_repo * @tparam string repopath The path to the repository * @treturn llrepo The repository (or nil on error) * @treturn string An error message (if repo is nil) */ /* Trivial Repository binding, GC will free the repo */ static int L_open_repo(lua_State *L) { const char *repopath = luaL_checkstring(L, 1); int ret; repo_et_al_t *real = lua_newuserdata(L, sizeof(*real)); real->repo = NULL; real->odb = NULL; ret = git_repository_open(&real->repo, repopath); if (ret != 0) { return push_git2_error(L, ret); } ret = git_repository_odb(&real->odb, real->repo); if (ret != 0) { push_git2_error(L, ret); git_repository_free(real->repo); return 2; } lua_pushvalue(L, lua_upvalueindex(1)); lua_setmetatable(L, -2); return 1; } /** * Dereference a symbolic ref. * * @function lookup_symbolic_ref * @tparam llrepo repository The repository to look up the ref in. * @tparam string ref The symbolic ref name to dereference. * @treturn string The symbolic referent (or nil on error) * @treturn string The error message if the referent is nil */ static int L_lookup_symbolic_ref(lua_State *L) { git_repository *repo = to_repo(L, 1); const char *refname = luaL_checkstring(L, 2); git_reference *ref = NULL; int ret; if ((ret = git_reference_lookup(&ref, repo, refname)) != 0) { return push_git2_error(L, ret); } if (git_reference_type(ref) != GIT_REF_SYMBOLIC) { git_reference_free(ref); lua_pushnil(L); lua_pushfstring(L, "%s is not symbolic", refname); return 2; } lua_pushstring(L, git_reference_symbolic_target(ref)); git_reference_free(ref); return 1; } static int format_oid(lua_State *L, const git_oid *oid) { char oidstr[40]; git_oid_fmt(oidstr, oid); lua_pushlstring(L, oidstr, 40); return 1; } /** * Lookup the SHA1 pointed at by a reference. * * @function lookup_sha_from_ref * @tparam llrepo repository The repository to look up the reference in. * @tparam string ref The reference to look up. * @treturn string The OID (SHA1) of the referent (or nil on error). * @treturn string The error message if the OID is nil. */ static int L_lookup_sha_from_ref(lua_State *L) { git_repository *repo = to_repo(L, 1); const char *refname = luaL_checkstring(L, 2); git_oid ref; int ret; if ((ret = git_reference_name_to_id(&ref, repo, refname)) != 0) { return push_git2_error(L, ret); } return format_oid(L, &ref); } static int parse_oid(lua_State *L, int spos, git_oid *oid) { const char *oid_s = luaL_checkstring(L, spos); int ret = git_oid_fromstr(oid, oid_s); if (ret != 0) return push_git2_error(L, ret); return 0; } /** * Calculate the merge-base of a pair of OIDs. * * @function merge_base * @tparam llrepo repository The repository to calculate the merge-base within. * @tparam string left The left-side of the merge as an OID. * @tparam string right The right-side of the merge as an OID. * @treturn string The OID of the merge-base if available, or nil on error. * @treturn string The error message if the merge-base is nil. */ static int L_merge_base(lua_State *L) { git_repository *repo = to_repo(L, 1); git_oid left, right, out; int ret; if (parse_oid(L, 2, &left) != 0) return 2; if (parse_oid(L, 3, &right) != 0) return 2; ret = git_merge_base(&out, repo, &left, &right); if (ret == 0) return format_oid(L, &out); if (ret == GIT_ENOTFOUND) { lua_pushnil(L); lua_pushliteral(L, "ENOTFOUND"); return 2; } return push_git2_error(L, ret); } /** * Set a symbolic reference's referent. * * @function set_symbolic_ref * @tparam llrepo repository The repository to set the reference within. * @tparam string reference The reference to set. * @tparam string referent The reference name to act as referent for reference. * @treturn boolean On success, true, otherwise nil. * @treturn string On failure, the error message. */ static int L_set_symbolic_ref(lua_State *L) { git_repository *repo = to_repo(L, 1); git_reference *ref; int ret; if ((ret = git_reference_symbolic_create(&ref, repo, luaL_checkstring(L, 2), luaL_checkstring(L, 3), 1, NULL #if LIBGIT2_VER_MINOR < 23 , NULL #endif )) != 0) { return push_git2_error(L, ret); } git_reference_free(ref); lua_pushboolean(L, 1); return 1; } static int L_gc_odb_object(lua_State *L) { git_odb_object **obj = lua_touserdata(L, 1); if (*obj != NULL) git_odb_object_free(*obj); *obj = NULL; return 0; } /** * Retrieve a generic object from git. * * @function get_object * @tparam llrepo repository The repository to retrieve the object from. * @tparam string oid The object to return (as a SHA1) * @treturn gitobject The git object whose OID was provided (or nil) * @treturn string The error message if the returned git object was nil */ static int L_get_object(lua_State *L) { git_odb *odb = to_odb(L, 1); git_odb_object **obj = lua_newuserdata(L, sizeof(*obj)); git_oid oid; int ret; if (parse_oid(L, 2, &oid) != 0) return 2; if ((ret = git_odb_read(obj, odb, &oid)) != 0) return push_git2_error(L, ret); lua_pushvalue(L, lua_upvalueindex(1)); lua_setmetatable(L, -2); return 1; } /** * Retrieve the size of a git object. * * @function get_object_size * @tparam gitobject obj The object whose size you wish to query * @treturn number The size of the object in bytes */ static int L_get_object_size(lua_State *L) { git_odb_object **obj = lua_touserdata(L, 1); lua_pushnumber(L, git_odb_object_size(*obj)); return 1; } static void push_object_type_str(lua_State *L, git_otype ty) { switch(ty) { case GIT_OBJ_ANY: lua_pushliteral(L, "any"); break; case GIT_OBJ_BAD: lua_pushliteral(L, "bad"); break; case GIT_OBJ__EXT1: lua_pushliteral(L, "reserved"); break; case GIT_OBJ_COMMIT: lua_pushliteral(L, "commit"); break; case GIT_OBJ_TREE: lua_pushliteral(L, "tree"); break; case GIT_OBJ_BLOB: lua_pushliteral(L, "blob"); break; case GIT_OBJ_TAG: lua_pushliteral(L, "tag"); break; case GIT_OBJ__EXT2: lua_pushliteral(L, "reserved"); break; case GIT_OBJ_OFS_DELTA: lua_pushliteral(L, "delta"); break; case GIT_OBJ_REF_DELTA: lua_pushliteral(L, "refdelta"); break; default: lua_pushliteral(L, "unknown"); break; } } /** * Retrieve a git object's type * * @function get_object_type * @tparam gitobject obj The object whose type you wish to retrieve. * @treturn string The type of the object provided. */ static int L_get_object_type(lua_State *L) { git_odb_object **obj = lua_touserdata(L, 1); push_object_type_str(L, git_odb_object_type(*obj)); return 1; } /** * Retrieve the raw content of an object * * @function get_object_raw * @tparam gitobject obj The object whose content you wish to retrieve. * @treturn string The raw content of the object as a string. */ static int L_get_object_raw(lua_State *L) { git_odb_object **obj = lua_touserdata(L, 1); lua_pushlstring(L, git_odb_object_data(*obj), git_odb_object_size(*obj)); return 1; } /** * Retrieve a tree's content as a table * * The returned table is a numerically indexed table of entries. Each entry * is a table with `name` (string) `sha` (string) and `perms` (number) entries. * * @function get_tree_table * @tparam llrepo repository The repository to query for the tree's content. * @tparam string tree The OID of the tree object to retrieve * @treturn table The tree as a table, or nil on error * @treturn string The error message if the returned tree table is nil */ static int L_get_tree_table(lua_State *L) { git_repository *repo = to_repo(L, 1); git_oid oid; git_tree *tree; size_t ent; int ret; if (parse_oid(L, 2, &oid) != 0) return 2; if ((ret = git_tree_lookup(&tree, repo, &oid)) != 0) return push_git2_error(L, ret); lua_newtable(L); for (ent = 0; ent < git_tree_entrycount(tree); ++ent) { const git_tree_entry *tree_ent = git_tree_entry_byindex(tree, ent); lua_pushnumber(L, ent+1); lua_newtable(L); lua_pushliteral(L, "name"); lua_pushstring(L, git_tree_entry_name(tree_ent)); lua_settable(L, -3); lua_pushliteral(L, "sha"); format_oid(L, git_tree_entry_id(tree_ent)); lua_settable(L, -3); lua_pushliteral(L, "perms"); lua_pushnumber(L, git_tree_entry_filemode_raw(tree_ent)); lua_settable(L, -3); lua_settable(L, -3); } git_tree_free(tree); return 1; } /** * Lookup the SHA1 pointed at by a sha1ish. * * @function revparse_single * @tparam llrepo repository The repository to look up the reference in. * @tparam string ref The sha1ish to look up. * @treturn string The OID (SHA1) of the referent (or nil on error). * @treturn string The error message if the OID is nil. */ static int L_revparse_single(lua_State *L) { git_repository *repo = to_repo(L, 1); const char *sha1ish = luaL_checkstring(L, 2); git_object *obj; int ret; if ((ret = git_revparse_single(&obj, repo, sha1ish)) != 0) { return push_git2_error(L, ret); } ret = format_oid(L, git_object_id(obj)); git_object_free(obj); return ret; } /** * The version of libgit2 which this instance of gall was built against. * * When Gall is compiled, the version of libgit2 is baked into the C binding * to be checked against when run. * * @field LIBGIT2_VERSION */ int luaopen_gall_ll_git2(lua_State *L) { lua_newtable(L); lua_pushliteral(L, "LIBGIT2_VERSION"); lua_pushliteral(L, LIBGIT2_VERSION); lua_settable(L, -3); lua_pushliteral(L, "open_repo"); lua_newtable(L); lua_pushliteral(L, "__gc"); lua_pushcclosure(L, L_gc_repo, 0); lua_settable(L, -3); lua_pushcclosure(L, L_open_repo, 1); lua_settable(L, -3); lua_pushliteral(L, "get_object"); lua_newtable(L); lua_pushliteral(L, "__gc"); lua_pushcclosure(L, L_gc_odb_object, 0); lua_settable(L, -3); lua_pushcclosure(L, L_get_object, 1); lua_settable(L, -3); #define BASIC_FUNC(FN) \ do { \ lua_pushliteral(L, #FN); \ lua_pushcclosure(L, L_##FN, 0); \ lua_settable(L, -3); \ } while (0) BASIC_FUNC(lookup_symbolic_ref); BASIC_FUNC(lookup_sha_from_ref); BASIC_FUNC(set_symbolic_ref); BASIC_FUNC(merge_base); BASIC_FUNC(get_object_size); BASIC_FUNC(get_object_type); BASIC_FUNC(get_object_raw); BASIC_FUNC(get_tree_table); BASIC_FUNC(revparse_single); #if LIBGIT2_SOVERSION > 22 git_libgit2_init(); #endif return 1; }