lib-core.git 0.2.0: HTTP-Basic token auth + ls-remote error classification + set_token
- Credential callback wired through clone, fetch, and async ls-remote. Token source: SPOREL_GITEA_TOKEN env at lib-init, overridable per-process via the new set_token(t) Lua binding (engine.config "gitea_token" reaches it through the launcher). - Async ls-remote take_result now emits a kind field on failure: "auth-required", "not-found", "network", or "other". Consumers can classify update-check failures (e.g. distinguish a private repo needing creds from a repo that doesn't exist at all from a flaky network). - DLL rebuilt with the new code; manifest bumped to 0.2.0.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"id": "lib-core.git",
|
"id": "lib-core.git",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"api_min": "0.1",
|
"api_min": "0.1",
|
||||||
"native": true,
|
"native": true,
|
||||||
"deps": []
|
"deps": []
|
||||||
|
|||||||
BIN
sporel_git.dll
BIN
sporel_git.dll
Binary file not shown.
@@ -33,6 +33,15 @@
|
|||||||
* sporel_lib_init; used by the async wrappers below. */
|
* sporel_lib_init; used by the async wrappers below. */
|
||||||
static const sporel_engine_api *g_api = NULL;
|
static const sporel_engine_api *g_api = NULL;
|
||||||
|
|
||||||
|
/* Optional Gitea / HTTP-Basic token. Populated at sporel_lib_init from
|
||||||
|
* SPOREL_GITEA_TOKEN. When set, every libgit2 transport request that
|
||||||
|
* asks for credentials gets username="token", password=g_token via the
|
||||||
|
* cred-acquire callback. Empty string = anonymous (callback declines).
|
||||||
|
*
|
||||||
|
* Read once at init; no per-call override yet. A future engine.config
|
||||||
|
* binding can refresh it (rebind via a setter) when settings UI lands. */
|
||||||
|
static char g_token[512] = "";
|
||||||
|
|
||||||
/* Call once per process; thread-safe via libgit2's own internal flag. */
|
/* Call once per process; thread-safe via libgit2's own internal flag. */
|
||||||
static void ensure_libgit2_init(void) {
|
static void ensure_libgit2_init(void) {
|
||||||
static int initialized = 0;
|
static int initialized = 0;
|
||||||
@@ -42,6 +51,45 @@ static void ensure_libgit2_init(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* HTTP-Basic credential callback. libgit2 invokes this when the remote
|
||||||
|
* demands auth. We service USERPASS_PLAINTEXT (Gitea token via
|
||||||
|
* Basic-Auth, username literally "token") and decline everything else
|
||||||
|
* (SSH-key types are unsupported since we ship no SSH transport). */
|
||||||
|
static int sporel_cred_acquire_cb(git_credential **out,
|
||||||
|
const char *url,
|
||||||
|
const char *username_from_url,
|
||||||
|
unsigned int allowed_types,
|
||||||
|
void *payload) {
|
||||||
|
(void)url; (void)username_from_url; (void)payload;
|
||||||
|
if (!(allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT)) {
|
||||||
|
return GIT_PASSTHROUGH;
|
||||||
|
}
|
||||||
|
if (g_token[0] == '\0') {
|
||||||
|
/* No token configured — fall through so libgit2 surfaces the
|
||||||
|
* raw "authentication required" error to the caller. */
|
||||||
|
return GIT_PASSTHROUGH;
|
||||||
|
}
|
||||||
|
return git_credential_userpass_plaintext_new(out, "token", g_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map a libgit2 error to a stable "kind" string the Lua side surfaces
|
||||||
|
* in result.kind. Lets the launcher distinguish local-only repos
|
||||||
|
* (404 with creds) from need-auth (401 without creds) from genuine
|
||||||
|
* network failures. Pattern-matches the error message because
|
||||||
|
* libgit2's error-class taxonomy varies across HTTP backends. */
|
||||||
|
static const char *classify_git_error(int rc, const git_error *e) {
|
||||||
|
const char *msg = (e && e->message) ? e->message : "";
|
||||||
|
if (rc == GIT_EAUTH) return "auth-required";
|
||||||
|
if (strstr(msg, "401") || strstr(msg, "authentication required")
|
||||||
|
|| strstr(msg, "Authentication")) return "auth-required";
|
||||||
|
if (strstr(msg, "404") || strstr(msg, "Not Found")
|
||||||
|
|| strstr(msg, "not found")) return "not-found";
|
||||||
|
if (e && (e->klass == GIT_ERROR_NET || e->klass == GIT_ERROR_HTTP
|
||||||
|
|| e->klass == GIT_ERROR_SSL))
|
||||||
|
return "network";
|
||||||
|
return "other";
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- Synchronous bindings ------------------------------------------------ */
|
/* ---- Synchronous bindings ------------------------------------------------ */
|
||||||
|
|
||||||
/* git.clone(url, dest_dir, ref) -> ok, err
|
/* git.clone(url, dest_dir, ref) -> ok, err
|
||||||
@@ -54,6 +102,7 @@ static int l_clone(lua_State *L) {
|
|||||||
git_repository *repo = NULL;
|
git_repository *repo = NULL;
|
||||||
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
|
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
|
||||||
if (ref) opts.checkout_branch = ref;
|
if (ref) opts.checkout_branch = ref;
|
||||||
|
opts.fetch_opts.callbacks.credentials = sporel_cred_acquire_cb;
|
||||||
|
|
||||||
int rc = git_clone(&repo, url, dest, &opts);
|
int rc = git_clone(&repo, url, dest, &opts);
|
||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
@@ -215,6 +264,7 @@ static int l_fetch(lua_State *L) {
|
|||||||
if (git_remote_create_anonymous(&remote, repo, url) != 0) goto err;
|
if (git_remote_create_anonymous(&remote, repo, url) != 0) goto err;
|
||||||
|
|
||||||
git_fetch_options fopts = GIT_FETCH_OPTIONS_INIT;
|
git_fetch_options fopts = GIT_FETCH_OPTIONS_INIT;
|
||||||
|
fopts.callbacks.credentials = sporel_cred_acquire_cb;
|
||||||
int rc = git_remote_fetch(remote, NULL, &fopts, NULL);
|
int rc = git_remote_fetch(remote, NULL, &fopts, NULL);
|
||||||
git_remote_free(remote);
|
git_remote_free(remote);
|
||||||
git_repository_free(repo);
|
git_repository_free(repo);
|
||||||
@@ -247,6 +297,7 @@ typedef struct {
|
|||||||
/* outputs: */
|
/* outputs: */
|
||||||
int ok;
|
int ok;
|
||||||
char err[512];
|
char err[512];
|
||||||
|
char kind[32]; /* "auth-required" | "not-found" | "network" | "other" */
|
||||||
int tag_count;
|
int tag_count;
|
||||||
char tags[GIT_ASYNC_MAX_TAGS][GIT_ASYNC_TAG_LEN];
|
char tags[GIT_ASYNC_MAX_TAGS][GIT_ASYNC_TAG_LEN];
|
||||||
} git_async_ctx;
|
} git_async_ctx;
|
||||||
@@ -261,6 +312,7 @@ static void do_ls_remote_tags(void *vctx) {
|
|||||||
if (rc != 0) goto fail;
|
if (rc != 0) goto fail;
|
||||||
|
|
||||||
git_remote_callbacks cbs = GIT_REMOTE_CALLBACKS_INIT;
|
git_remote_callbacks cbs = GIT_REMOTE_CALLBACKS_INIT;
|
||||||
|
cbs.credentials = sporel_cred_acquire_cb;
|
||||||
rc = git_remote_connect(remote, GIT_DIRECTION_FETCH, &cbs, NULL, NULL);
|
rc = git_remote_connect(remote, GIT_DIRECTION_FETCH, &cbs, NULL, NULL);
|
||||||
if (rc != 0) goto fail;
|
if (rc != 0) goto fail;
|
||||||
|
|
||||||
@@ -289,6 +341,7 @@ fail:;
|
|||||||
const git_error *e = git_error_last();
|
const git_error *e = git_error_last();
|
||||||
snprintf(c->err, sizeof c->err, "%s",
|
snprintf(c->err, sizeof c->err, "%s",
|
||||||
e && e->message ? e->message : "ls_remote failed");
|
e && e->message ? e->message : "ls_remote failed");
|
||||||
|
snprintf(c->kind, sizeof c->kind, "%s", classify_git_error(rc, e));
|
||||||
c->ok = 0;
|
c->ok = 0;
|
||||||
if (remote) git_remote_free(remote);
|
if (remote) git_remote_free(remote);
|
||||||
}
|
}
|
||||||
@@ -301,6 +354,22 @@ typedef struct {
|
|||||||
git_async_ctx ctx; /* Lua-owned via userdata */
|
git_async_ctx ctx; /* Lua-owned via userdata */
|
||||||
} git_handle_userdata;
|
} git_handle_userdata;
|
||||||
|
|
||||||
|
/* git.set_token(token | nil) -> nil
|
||||||
|
* Replaces the in-memory Gitea HTTP-Basic token used by the credential
|
||||||
|
* callback. Passing nil / empty string reverts to anonymous. Source of
|
||||||
|
* truth at lib-init is SPOREL_GITEA_TOKEN env; the launcher (or any
|
||||||
|
* consumer) calls this to override with engine.config("gitea_token")
|
||||||
|
* once the Lua engine bindings are live. */
|
||||||
|
static int l_set_token(lua_State *L) {
|
||||||
|
if (lua_isnoneornil(L, 1)) {
|
||||||
|
g_token[0] = '\0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const char *t = luaL_checkstring(L, 1);
|
||||||
|
snprintf(g_token, sizeof g_token, "%s", t);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* git.start_ls_remote_tags(url) -> handle (userdata) */
|
/* git.start_ls_remote_tags(url) -> handle (userdata) */
|
||||||
static int l_start_ls_remote_tags(lua_State *L) {
|
static int l_start_ls_remote_tags(lua_State *L) {
|
||||||
if (!g_api) {
|
if (!g_api) {
|
||||||
@@ -349,6 +418,8 @@ static int l_take_result(lua_State *L) {
|
|||||||
} else {
|
} else {
|
||||||
lua_pushstring(L, u->ctx.err);
|
lua_pushstring(L, u->ctx.err);
|
||||||
lua_setfield(L, -2, "error");
|
lua_setfield(L, -2, "error");
|
||||||
|
lua_pushstring(L, u->ctx.kind);
|
||||||
|
lua_setfield(L, -2, "kind");
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -359,6 +430,12 @@ SPOREL_EXPORT int sporel_lib_init(lua_State *L, const sporel_engine_api *api) {
|
|||||||
g_api = api;
|
g_api = api;
|
||||||
ensure_libgit2_init();
|
ensure_libgit2_init();
|
||||||
|
|
||||||
|
/* Latch the Gitea token from env, if present. Empty/unset → anon. */
|
||||||
|
const char *tok = getenv("SPOREL_GITEA_TOKEN");
|
||||||
|
if (tok && *tok) {
|
||||||
|
snprintf(g_token, sizeof g_token, "%s", tok);
|
||||||
|
}
|
||||||
|
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
|
|
||||||
lua_pushcfunction(L, l_clone); lua_setfield(L, -2, "clone");
|
lua_pushcfunction(L, l_clone); lua_setfield(L, -2, "clone");
|
||||||
@@ -372,6 +449,7 @@ SPOREL_EXPORT int sporel_lib_init(lua_State *L, const sporel_engine_api *api) {
|
|||||||
lua_pushcfunction(L, l_start_ls_remote_tags); lua_setfield(L, -2, "start_ls_remote_tags");
|
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_is_done); lua_setfield(L, -2, "is_done");
|
||||||
lua_pushcfunction(L, l_take_result); lua_setfield(L, -2, "take_result");
|
lua_pushcfunction(L, l_take_result); lua_setfield(L, -2, "take_result");
|
||||||
|
lua_pushcfunction(L, l_set_token); lua_setfield(L, -2, "set_token");
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user