lib-core.git v0.1.0: native libgit2 wrapper
Initial release. Statically vendors libgit2 v1.7.2 (HTTPS-only via WinHTTP on Windows, no SSH dependency); exposes synchronous clone, checkout, fetch, describe, has_ref, is_repo, tags plus an async start_ls_remote_tags / is_done / take_result triple routed through the engine async-pool. The native-lib loader resolves engine services through the sporel_engine_api struct passed to sporel_lib_init, so this lib does not statically depend on internal engine symbols beyond the Lua C-API re-exports. Smoke-tested against a public Gitea repo: clone over HTTPS succeeds, async ls-remote returns the tag list, all C-allocations clean up via git_*_free / git_buf_dispose. First clean build of libgit2 takes ~5-15 min on a typical machine; cached afterwards.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build/
|
||||
110
CMakeLists.txt
Normal file
110
CMakeLists.txt
Normal file
@@ -0,0 +1,110 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(sporel_lib_core_git LANGUAGES C)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Vendor libgit2 v1.7.2 — static, HTTPS-only, no SSH, no CLI, no tests.
|
||||
# HTTPS backend: WinHTTP on Windows; libgit2 picks it automatically when
|
||||
# USE_HTTPS=ON and BUILD_SHARED_LIBS=OFF. See ADR-0047.
|
||||
# First fetch + build takes ~5-15 min; subsequent builds are fast.
|
||||
# -----------------------------------------------------------------------------
|
||||
include(FetchContent)
|
||||
set(BUILD_CLI OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(USE_SSH OFF CACHE BOOL "" FORCE) # HTTPS-only per spec
|
||||
set(USE_HTTPS ON CACHE BOOL "" FORCE)
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) # static
|
||||
set(REGEX_BACKEND builtin CACHE STRING "" FORCE)
|
||||
|
||||
FetchContent_Declare(
|
||||
libgit2
|
||||
GIT_REPOSITORY https://github.com/libgit2/libgit2.git
|
||||
GIT_TAG v1.7.2)
|
||||
FetchContent_MakeAvailable(libgit2)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Lua headers: reused from the engine's FetchContent cache (the same Lua
|
||||
# VM Sporel.exe boots into; no separate Lua build for the lib). At runtime,
|
||||
# Lua C-API symbols (lua_*, luaL_*) are resolved against the re-exports
|
||||
# in sporel_engine.dll (per ADR-0023).
|
||||
#
|
||||
# At link time on MinGW we need an import library that resolves those
|
||||
# symbols; we use libsporel_engine.dll.a from the engine's build tree.
|
||||
# Caller MUST set both -DLUA_INCLUDE_DIR_HINT and -DSPOREL_ENGINE_BUILD_HINT.
|
||||
# -----------------------------------------------------------------------------
|
||||
if(NOT DEFINED LUA_INCLUDE_DIR_HINT)
|
||||
set(LUA_INCLUDE_DIR_HINT
|
||||
"$ENV{HOME}/Projects/Sporel/sporel-engine/build/_deps/lua-src/src")
|
||||
endif()
|
||||
if(NOT DEFINED SPOREL_ENGINE_BUILD_HINT)
|
||||
set(SPOREL_ENGINE_BUILD_HINT
|
||||
"$ENV{HOME}/Projects/Sporel/sporel-engine/build")
|
||||
endif()
|
||||
if(NOT DEFINED SPOREL_ENGINE_INCLUDE_HINT)
|
||||
set(SPOREL_ENGINE_INCLUDE_HINT
|
||||
"$ENV{HOME}/Projects/Sporel/sporel-engine/include")
|
||||
endif()
|
||||
if(NOT DEFINED SPOREL_ENGINE_SRC_HINT)
|
||||
# engine_async.h lives in src/ (private engine headers). Native libs
|
||||
# that use the async-pool need this for the sporel_async_handle and
|
||||
# sporel_async_work_fn typedefs — though engine_api.h re-declares them.
|
||||
set(SPOREL_ENGINE_SRC_HINT
|
||||
"$ENV{HOME}/Projects/Sporel/sporel-engine/src")
|
||||
endif()
|
||||
|
||||
find_path(LUA_INCLUDE_DIR lua.h HINTS ${LUA_INCLUDE_DIR_HINT})
|
||||
if(NOT LUA_INCLUDE_DIR)
|
||||
message(FATAL_ERROR
|
||||
"sporel_git: could not find lua.h. Pass "
|
||||
"-DLUA_INCLUDE_DIR_HINT=/path/to/sporel-engine/build/_deps/lua-src/src "
|
||||
"(default = ~/Projects/Sporel/sporel-engine/build/_deps/lua-src/src).")
|
||||
endif()
|
||||
|
||||
find_library(SPOREL_ENGINE_IMPLIB
|
||||
NAMES sporel_engine libsporel_engine
|
||||
HINTS ${SPOREL_ENGINE_BUILD_HINT}
|
||||
NO_DEFAULT_PATH)
|
||||
if(NOT SPOREL_ENGINE_IMPLIB)
|
||||
message(FATAL_ERROR
|
||||
"sporel_git: could not find libsporel_engine.dll.a in "
|
||||
"${SPOREL_ENGINE_BUILD_HINT}. Build the engine first.")
|
||||
endif()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# sporel_git shared module
|
||||
# -----------------------------------------------------------------------------
|
||||
add_library(sporel_git MODULE src/sporel_git.c)
|
||||
|
||||
target_include_directories(sporel_git PRIVATE
|
||||
src
|
||||
${LUA_INCLUDE_DIR}
|
||||
${SPOREL_ENGINE_INCLUDE_HINT} # for sporel/engine_api.h
|
||||
${libgit2_SOURCE_DIR}/include)
|
||||
|
||||
# libgit2's main target is named "libgit2package" (the on-disk artifact
|
||||
# is libgit2.a / git2.dll; CMake target name lives independently per
|
||||
# libgit2's CMakeLists.txt at src/libgit2/CMakeLists.txt:59).
|
||||
# Linking against the CMake target propagates transitive system libs
|
||||
# (WinHTTP, ws2_32, crypt32, etc.) automatically.
|
||||
target_link_libraries(sporel_git PRIVATE
|
||||
libgit2package
|
||||
${SPOREL_ENGINE_IMPLIB})
|
||||
|
||||
# When linking into sporel_engine.dll at runtime the Lua symbols come from
|
||||
# its re-exports, so we treat the engine import lib as the Lua import lib
|
||||
# too (no separate lua54 target). This avoids needing two Lua copies.
|
||||
|
||||
set_target_properties(sporel_git PROPERTIES
|
||||
PREFIX ""
|
||||
OUTPUT_NAME "sporel_git")
|
||||
|
||||
# Silence libgit2's compile warnings — they leak through FetchContent.
|
||||
if(MSVC)
|
||||
target_compile_options(sporel_git PRIVATE /W2)
|
||||
else()
|
||||
target_compile_options(sporel_git PRIVATE -Wall -Wno-unused-parameter)
|
||||
endif()
|
||||
|
||||
# On Linux: --unresolved-symbols=ignore-in-shared-libs would let us drop
|
||||
# the engine import lib, but the import-lib path is uniform across both
|
||||
# platforms — keep it.
|
||||
7
manifest.lib
Normal file
7
manifest.lib
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"id": "lib-core.git",
|
||||
"version": "0.1.0",
|
||||
"api_min": "0.1",
|
||||
"native": true,
|
||||
"deps": []
|
||||
}
|
||||
BIN
sporel_git.dll
Normal file
BIN
sporel_git.dll
Normal file
Binary file not shown.
377
src/sporel_git.c
Normal file
377
src/sporel_git.c
Normal file
@@ -0,0 +1,377 @@
|
||||
/* sporel_git.c — lib-core.git v0.1.0
|
||||
*
|
||||
* Native libgit2 wrapper exposed to Lua. Synchronous + asynchronous API.
|
||||
* All async operations dispatch through the engine's worker-thread pool
|
||||
* via the sporel_engine_api struct passed to sporel_lib_init (Slice 4
|
||||
* ADR-0046).
|
||||
*
|
||||
* Transport: HTTPS only (WinHTTP on Windows, OpenSSL/mbedTLS on Linux
|
||||
* depending on libgit2's autodetection). No SSH support — keeps the
|
||||
* static link surface small and avoids libssh2 + dependent crypto.
|
||||
*
|
||||
* Threading model: libgit2 is fully thread-safe AFTER git_libgit2_init.
|
||||
* We init once on sporel_lib_init. The async worker that runs ls-remote
|
||||
* is the only place we touch libgit2 from a non-Lua thread.
|
||||
*/
|
||||
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
#include "git2.h"
|
||||
#include "sporel/engine_api.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# define SPOREL_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
# define SPOREL_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
/* Pointer to the engine-provided API struct. Populated by
|
||||
* sporel_lib_init; used by the async wrappers below. */
|
||||
static const sporel_engine_api *g_api = NULL;
|
||||
|
||||
/* Call once per process; thread-safe via libgit2's own internal flag. */
|
||||
static void ensure_libgit2_init(void) {
|
||||
static int initialized = 0;
|
||||
if (!initialized) {
|
||||
git_libgit2_init();
|
||||
initialized = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Synchronous bindings ------------------------------------------------ */
|
||||
|
||||
/* git.clone(url, dest_dir, ref) -> ok, err
|
||||
* ref is optional — when set, libgit2 checks out that branch after clone. */
|
||||
static int l_clone(lua_State *L) {
|
||||
const char *url = luaL_checkstring(L, 1);
|
||||
const char *dest = luaL_checkstring(L, 2);
|
||||
const char *ref = luaL_optstring(L, 3, NULL);
|
||||
|
||||
git_repository *repo = NULL;
|
||||
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
|
||||
if (ref) opts.checkout_branch = ref;
|
||||
|
||||
int rc = git_clone(&repo, url, dest, &opts);
|
||||
if (rc != 0) {
|
||||
lua_pushboolean(L, 0);
|
||||
const git_error *e = git_error_last();
|
||||
lua_pushstring(L, e ? e->message : "git_clone failed");
|
||||
return 2;
|
||||
}
|
||||
git_repository_free(repo);
|
||||
lua_pushboolean(L, 1);
|
||||
lua_pushnil(L);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* git.checkout(dir, ref) -> ok, err
|
||||
* Resolves the ref (tag, branch or sha) and detaches HEAD onto its commit. */
|
||||
static int l_checkout(lua_State *L) {
|
||||
const char *dir = luaL_checkstring(L, 1);
|
||||
const char *ref = luaL_checkstring(L, 2);
|
||||
|
||||
git_repository *repo = NULL;
|
||||
git_object *target = NULL;
|
||||
|
||||
int rc = git_repository_open(&repo, dir);
|
||||
if (rc != 0) goto err;
|
||||
|
||||
rc = git_revparse_single(&target, repo, ref);
|
||||
if (rc != 0) goto err;
|
||||
|
||||
git_checkout_options copts = GIT_CHECKOUT_OPTIONS_INIT;
|
||||
copts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
rc = git_checkout_tree(repo, target, &copts);
|
||||
if (rc != 0) goto err;
|
||||
|
||||
rc = git_repository_set_head_detached(repo, git_object_id(target));
|
||||
if (rc != 0) goto err;
|
||||
|
||||
git_object_free(target);
|
||||
git_repository_free(repo);
|
||||
lua_pushboolean(L, 1);
|
||||
lua_pushnil(L);
|
||||
return 2;
|
||||
|
||||
err:;
|
||||
const git_error *e = git_error_last();
|
||||
lua_pushboolean(L, 0);
|
||||
lua_pushstring(L, e ? e->message : "checkout failed");
|
||||
if (target) git_object_free(target);
|
||||
if (repo) git_repository_free(repo);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* git.describe(dir) -> desc_str or nil, err */
|
||||
static int l_describe(lua_State *L) {
|
||||
const char *dir = luaL_checkstring(L, 1);
|
||||
|
||||
git_repository *repo = NULL;
|
||||
git_describe_result *result = NULL;
|
||||
git_buf out = { 0 };
|
||||
|
||||
int rc = git_repository_open(&repo, dir);
|
||||
if (rc != 0) goto err;
|
||||
|
||||
git_describe_options dopts = GIT_DESCRIBE_OPTIONS_INIT;
|
||||
dopts.describe_strategy = GIT_DESCRIBE_TAGS;
|
||||
rc = git_describe_workdir(&result, repo, &dopts);
|
||||
if (rc != 0) goto err;
|
||||
|
||||
git_describe_format_options fopts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT;
|
||||
fopts.dirty_suffix = "-dirty";
|
||||
rc = git_describe_format(&out, result, &fopts);
|
||||
if (rc != 0) goto err;
|
||||
|
||||
lua_pushstring(L, out.ptr);
|
||||
lua_pushnil(L);
|
||||
git_buf_dispose(&out);
|
||||
git_describe_result_free(result);
|
||||
git_repository_free(repo);
|
||||
return 2;
|
||||
|
||||
err:;
|
||||
const git_error *e = git_error_last();
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, e ? e->message : "describe failed");
|
||||
if (result) git_describe_result_free(result);
|
||||
if (repo) git_repository_free(repo);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* git.has_ref(dir, ref) -> bool */
|
||||
static int l_has_ref(lua_State *L) {
|
||||
const char *dir = luaL_checkstring(L, 1);
|
||||
const char *ref = luaL_checkstring(L, 2);
|
||||
git_repository *repo = NULL;
|
||||
if (git_repository_open(&repo, dir) != 0) {
|
||||
lua_pushboolean(L, 0);
|
||||
return 1;
|
||||
}
|
||||
git_object *o = NULL;
|
||||
int rc = git_revparse_single(&o, repo, ref);
|
||||
lua_pushboolean(L, rc == 0);
|
||||
if (o) git_object_free(o);
|
||||
git_repository_free(repo);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* git.is_repo(dir) -> bool */
|
||||
static int l_is_repo(lua_State *L) {
|
||||
const char *dir = luaL_checkstring(L, 1);
|
||||
git_repository *repo = NULL;
|
||||
int rc = git_repository_open(&repo, dir);
|
||||
if (rc == 0) {
|
||||
git_repository_free(repo);
|
||||
lua_pushboolean(L, 1);
|
||||
} else {
|
||||
lua_pushboolean(L, 0);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* git.tags(dir) -> {tag1, tag2, ...}, err */
|
||||
static int l_tags(lua_State *L) {
|
||||
const char *dir = luaL_checkstring(L, 1);
|
||||
git_repository *repo = NULL;
|
||||
if (git_repository_open(&repo, dir) != 0) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "not a repo");
|
||||
return 2;
|
||||
}
|
||||
git_strarray names = { 0 };
|
||||
int rc = git_tag_list(&names, repo);
|
||||
if (rc != 0) {
|
||||
const git_error *e = git_error_last();
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, e ? e->message : "git_tag_list failed");
|
||||
git_repository_free(repo);
|
||||
return 2;
|
||||
}
|
||||
lua_createtable(L, (int)names.count, 0);
|
||||
for (size_t i = 0; i < names.count; i++) {
|
||||
lua_pushstring(L, names.strings[i]);
|
||||
lua_rawseti(L, -2, (int)(i + 1));
|
||||
}
|
||||
git_strarray_dispose(&names);
|
||||
git_repository_free(repo);
|
||||
lua_pushnil(L);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* git.fetch(url, dir) -> ok, err
|
||||
* Anonymous-remote fetch — does not mutate origin's refspecs. */
|
||||
static int l_fetch(lua_State *L) {
|
||||
const char *url = luaL_checkstring(L, 1);
|
||||
const char *dir = luaL_checkstring(L, 2);
|
||||
git_repository *repo = NULL;
|
||||
git_remote *remote = NULL;
|
||||
|
||||
if (git_repository_open(&repo, dir) != 0) goto err;
|
||||
if (git_remote_create_anonymous(&remote, repo, url) != 0) goto err;
|
||||
|
||||
git_fetch_options fopts = GIT_FETCH_OPTIONS_INIT;
|
||||
int rc = git_remote_fetch(remote, NULL, &fopts, NULL);
|
||||
git_remote_free(remote);
|
||||
git_repository_free(repo);
|
||||
if (rc != 0) {
|
||||
const git_error *e = git_error_last();
|
||||
lua_pushboolean(L, 0);
|
||||
lua_pushstring(L, e ? e->message : "fetch failed");
|
||||
return 2;
|
||||
}
|
||||
lua_pushboolean(L, 1);
|
||||
lua_pushnil(L);
|
||||
return 2;
|
||||
|
||||
err:;
|
||||
const git_error *e = git_error_last();
|
||||
lua_pushboolean(L, 0);
|
||||
lua_pushstring(L, e ? e->message : "fetch open failed");
|
||||
if (remote) git_remote_free(remote);
|
||||
if (repo) git_repository_free(repo);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* ---- Asynchronous bindings ----------------------------------------------- */
|
||||
|
||||
#define GIT_ASYNC_MAX_TAGS 256
|
||||
#define GIT_ASYNC_TAG_LEN 128
|
||||
|
||||
typedef struct {
|
||||
char url[1024];
|
||||
/* outputs: */
|
||||
int ok;
|
||||
char err[512];
|
||||
int tag_count;
|
||||
char tags[GIT_ASYNC_MAX_TAGS][GIT_ASYNC_TAG_LEN];
|
||||
} git_async_ctx;
|
||||
|
||||
/* Worker body — runs on an engine_async pool thread. Must NOT touch the
|
||||
* Lua state (single-threaded). All result-data is written into ctx. */
|
||||
static void do_ls_remote_tags(void *vctx) {
|
||||
git_async_ctx *c = (git_async_ctx *)vctx;
|
||||
git_remote *remote = NULL;
|
||||
|
||||
int rc = git_remote_create_detached(&remote, c->url);
|
||||
if (rc != 0) goto fail;
|
||||
|
||||
git_remote_callbacks cbs = GIT_REMOTE_CALLBACKS_INIT;
|
||||
rc = git_remote_connect(remote, GIT_DIRECTION_FETCH, &cbs, NULL, NULL);
|
||||
if (rc != 0) goto fail;
|
||||
|
||||
const git_remote_head **heads = NULL;
|
||||
size_t n = 0;
|
||||
rc = git_remote_ls(&heads, &n, remote);
|
||||
if (rc != 0) goto fail;
|
||||
|
||||
c->tag_count = 0;
|
||||
for (size_t i = 0; i < n && c->tag_count < GIT_ASYNC_MAX_TAGS; i++) {
|
||||
if (strncmp(heads[i]->name, "refs/tags/", 10) == 0) {
|
||||
/* Skip the peeled-tag duplicates (^{}) that ls-remote can emit. */
|
||||
const char *name = heads[i]->name + 10;
|
||||
size_t nlen = strlen(name);
|
||||
if (nlen >= 3 && strcmp(name + nlen - 3, "^{}") == 0) continue;
|
||||
snprintf(c->tags[c->tag_count], GIT_ASYNC_TAG_LEN, "%s", name);
|
||||
c->tag_count++;
|
||||
}
|
||||
}
|
||||
c->ok = 1;
|
||||
git_remote_disconnect(remote);
|
||||
git_remote_free(remote);
|
||||
return;
|
||||
|
||||
fail:;
|
||||
const git_error *e = git_error_last();
|
||||
snprintf(c->err, sizeof c->err, "%s",
|
||||
e && e->message ? e->message : "ls_remote failed");
|
||||
c->ok = 0;
|
||||
if (remote) git_remote_free(remote);
|
||||
}
|
||||
|
||||
/* Lua-owned handle for an in-flight async op. We allocate it via
|
||||
* lua_newuserdata so the GC owns its memory; the engine-pool handle is
|
||||
* released in take_result. */
|
||||
typedef struct {
|
||||
sporel_async_handle *h; /* engine-owned; freed by take_result */
|
||||
git_async_ctx ctx; /* Lua-owned via userdata */
|
||||
} git_handle_userdata;
|
||||
|
||||
/* git.start_ls_remote_tags(url) -> handle (userdata) */
|
||||
static int l_start_ls_remote_tags(lua_State *L) {
|
||||
if (!g_api) {
|
||||
return luaL_error(L,
|
||||
"sporel_git: engine_api not wired (sporel_lib_init bug)");
|
||||
}
|
||||
const char *url = luaL_checkstring(L, 1);
|
||||
git_handle_userdata *u =
|
||||
(git_handle_userdata *)lua_newuserdata(L, sizeof(*u));
|
||||
memset(u, 0, sizeof(*u));
|
||||
snprintf(u->ctx.url, sizeof u->ctx.url, "%s", url);
|
||||
u->h = g_api->async_submit(do_ls_remote_tags, &u->ctx, NULL, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* git.is_done(handle) -> bool */
|
||||
static int l_is_done(lua_State *L) {
|
||||
git_handle_userdata *u = (git_handle_userdata *)lua_touserdata(L, 1);
|
||||
if (!u) return luaL_error(L, "git.is_done: expected userdata");
|
||||
if (!g_api) return luaL_error(L, "sporel_git: engine_api not wired");
|
||||
lua_pushboolean(L, g_api->async_is_done(u->h));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* git.take_result(handle) -> {tags = {...}} | {error = "..."} | nil, err */
|
||||
static int l_take_result(lua_State *L) {
|
||||
git_handle_userdata *u =
|
||||
(git_handle_userdata *)lua_touserdata(L, 1);
|
||||
if (!u) return luaL_error(L, "git.take_result: expected userdata");
|
||||
if (!g_api) return luaL_error(L, "sporel_git: engine_api not wired");
|
||||
if (!g_api->async_take_result(u->h)) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "not done");
|
||||
return 2;
|
||||
}
|
||||
u->h = NULL; /* engine freed the handle */
|
||||
|
||||
lua_newtable(L);
|
||||
if (u->ctx.ok) {
|
||||
lua_createtable(L, u->ctx.tag_count, 0);
|
||||
for (int i = 0; i < u->ctx.tag_count; i++) {
|
||||
lua_pushstring(L, u->ctx.tags[i]);
|
||||
lua_rawseti(L, -2, i + 1);
|
||||
}
|
||||
lua_setfield(L, -2, "tags");
|
||||
} else {
|
||||
lua_pushstring(L, u->ctx.err);
|
||||
lua_setfield(L, -2, "error");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- sporel_lib_init ----------------------------------------------------- */
|
||||
|
||||
SPOREL_EXPORT int sporel_lib_init(lua_State *L, const sporel_engine_api *api) {
|
||||
g_api = api;
|
||||
ensure_libgit2_init();
|
||||
|
||||
lua_newtable(L);
|
||||
|
||||
lua_pushcfunction(L, l_clone); lua_setfield(L, -2, "clone");
|
||||
lua_pushcfunction(L, l_checkout); lua_setfield(L, -2, "checkout");
|
||||
lua_pushcfunction(L, l_describe); lua_setfield(L, -2, "describe");
|
||||
lua_pushcfunction(L, l_has_ref); lua_setfield(L, -2, "has_ref");
|
||||
lua_pushcfunction(L, l_is_repo); lua_setfield(L, -2, "is_repo");
|
||||
lua_pushcfunction(L, l_tags); lua_setfield(L, -2, "tags");
|
||||
lua_pushcfunction(L, l_fetch); lua_setfield(L, -2, "fetch");
|
||||
|
||||
lua_pushcfunction(L, l_start_ls_remote_tags); lua_setfield(L, -2, "start_ls_remote_tags");
|
||||
lua_pushcfunction(L, l_is_done); lua_setfield(L, -2, "is_done");
|
||||
lua_pushcfunction(L, l_take_result); lua_setfield(L, -2, "take_result");
|
||||
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user