aboutsummaryrefslogtreecommitdiff
path: root/git2d
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--git2d/.gitignore1
-rw-r--r--git2d/bare.c309
-rw-r--r--git2d/bare.h72
-rw-r--r--git2d/cmd1.c129
-rw-r--r--git2d/cmd2.c126
-rw-r--r--git2d/main.c82
-rw-r--r--git2d/rw.c34
-rw-r--r--git2d/session.c78
-rw-r--r--git2d/x.h38
9 files changed, 869 insertions, 0 deletions
diff --git a/git2d/.gitignore b/git2d/.gitignore
new file mode 100644
index 0000000..635d84d
--- /dev/null
+++ b/git2d/.gitignore
@@ -0,0 +1 @@
+/git2d
diff --git a/git2d/bare.c b/git2d/bare.c
new file mode 100644
index 0000000..b580980
--- /dev/null
+++ b/git2d/bare.c
@@ -0,0 +1,309 @@
+/*-
+ * SPDX-License-Identifier: MIT
+ * SPDX-FileCopyrightText: Copyright (c) 2022 Frank Smit <https://61924.nl/>
+ */
+
+#include <string.h>
+#include <stdbool.h>
+
+#include "bare.h"
+
+#define UNUSED(x) (void)(x)
+
+enum {
+ U8SZ = 1,
+ U16SZ = 2,
+ U32SZ = 4,
+ U64SZ = 8,
+ MAXVARINTSZ = 10,
+};
+
+bare_error bare_put_uint(struct bare_writer *ctx, uint64_t x)
+{
+ uint64_t i = 0;
+ uint8_t b[MAXVARINTSZ];
+
+ while (x >= 0x80) {
+ b[i] = (uint8_t) x | 0x80;
+ x >>= 7;
+ i++;
+ }
+
+ b[i] = (uint8_t) x;
+ i++;
+
+ return ctx->write(ctx->buffer, b, i);
+}
+
+bare_error bare_get_uint(struct bare_reader *ctx, uint64_t *x)
+{
+ bare_error err = BARE_ERROR_NONE;
+
+ uint8_t shift = 0;
+ uint64_t result = 0;
+
+ for (uint8_t i = 0; i < 10; i++) {
+ uint8_t b;
+
+ err = ctx->read(ctx->buffer, &b, U8SZ);
+ if (err != BARE_ERROR_NONE) {
+ break;
+ }
+
+ if (b < 0x80) {
+ result |= (uint64_t) b << shift;
+ break;
+ } else {
+ result |= ((uint64_t) b & 0x7f) << shift;
+ shift += 7;
+ }
+ }
+
+ *x = result;
+
+ return err;
+}
+
+bare_error bare_put_int(struct bare_writer *ctx, int64_t x)
+{
+ uint64_t ux = (uint64_t) x << 1;
+
+ if (x < 0) {
+ ux = ~ux;
+ }
+
+ return bare_put_uint(ctx, ux);
+}
+
+bare_error bare_get_int(struct bare_reader *ctx, int64_t *x)
+{
+ uint64_t ux;
+
+ bare_error err = bare_get_uint(ctx, &ux);
+
+ if (err == BARE_ERROR_NONE) {
+ *x = (int64_t) (ux >> 1);
+
+ if ((ux & 1) != 0) {
+ *x = ~(*x);
+ }
+ }
+
+ return err;
+}
+
+bare_error bare_put_u8(struct bare_writer *ctx, uint8_t x)
+{
+ return ctx->write(ctx->buffer, &x, U8SZ);
+}
+
+bare_error bare_get_u8(struct bare_reader *ctx, uint8_t *x)
+{
+ return ctx->read(ctx->buffer, x, U8SZ);
+}
+
+bare_error bare_put_u16(struct bare_writer *ctx, uint16_t x)
+{
+ return ctx->write(ctx->buffer, (uint8_t[U16SZ]) {
+ x, x >> 8}
+ , U16SZ);
+}
+
+bare_error bare_get_u16(struct bare_reader *ctx, uint16_t *x)
+{
+ bare_error err = ctx->read(ctx->buffer, x, U16SZ);
+
+ if (err == BARE_ERROR_NONE) {
+ *x = (uint16_t) ((uint8_t *) x)[0]
+ | (uint16_t) ((uint8_t *) x)[1] << 8;
+ }
+
+ return err;
+}
+
+bare_error bare_put_u32(struct bare_writer *ctx, uint32_t x)
+{
+ uint8_t buf[U32SZ];
+
+ buf[0] = (uint8_t) (x);
+ buf[1] = (uint8_t) (x >> 8);
+ buf[2] = (uint8_t) (x >> 16);
+ buf[3] = (uint8_t) (x >> 24);
+
+ return ctx->write(ctx->buffer, buf, U32SZ);
+}
+
+bare_error bare_get_u32(struct bare_reader *ctx, uint32_t *x)
+{
+ bare_error err = ctx->read(ctx->buffer, x, U32SZ);
+
+ if (err == BARE_ERROR_NONE) {
+ *x = (uint32_t) (((uint8_t *) x)[0])
+ | (uint32_t) (((uint8_t *) x)[1] << 8)
+ | (uint32_t) (((uint8_t *) x)[2] << 16)
+ | (uint32_t) (((uint8_t *) x)[3] << 24);
+ }
+
+ return err;
+}
+
+bare_error bare_put_u64(struct bare_writer *ctx, uint64_t x)
+{
+ uint8_t buf[U64SZ];
+
+ buf[0] = x;
+ buf[1] = x >> 8;
+ buf[2] = x >> 16;
+ buf[3] = x >> 24;
+ buf[4] = x >> 32;
+ buf[5] = x >> 40;
+ buf[6] = x >> 48;
+ buf[7] = x >> 56;
+
+ return ctx->write(ctx->buffer, buf, U64SZ);
+}
+
+bare_error bare_get_u64(struct bare_reader *ctx, uint64_t *x)
+{
+ bare_error err = ctx->read(ctx->buffer, x, U64SZ);
+
+ if (err == BARE_ERROR_NONE) {
+ *x = (uint64_t) ((uint8_t *) x)[0]
+ | (uint64_t) ((uint8_t *) x)[1] << 8
+ | (uint64_t) ((uint8_t *) x)[2] << 16
+ | (uint64_t) ((uint8_t *) x)[3] << 24
+ | (uint64_t) ((uint8_t *) x)[4] << 32
+ | (uint64_t) ((uint8_t *) x)[5] << 40
+ | (uint64_t) ((uint8_t *) x)[6] << 48
+ | (uint64_t) ((uint8_t *) x)[7] << 56;
+ }
+
+ return err;
+}
+
+bare_error bare_put_i8(struct bare_writer *ctx, int8_t x)
+{
+ return bare_put_u8(ctx, x);
+}
+
+bare_error bare_get_i8(struct bare_reader *ctx, int8_t *x)
+{
+ return bare_get_u8(ctx, (uint8_t *) x);
+}
+
+bare_error bare_put_i16(struct bare_writer *ctx, int16_t x)
+{
+ return bare_put_u16(ctx, x);
+}
+
+bare_error bare_get_i16(struct bare_reader *ctx, int16_t *x)
+{
+ return bare_get_u16(ctx, (uint16_t *) x);
+}
+
+bare_error bare_put_i32(struct bare_writer *ctx, int32_t x)
+{
+ return bare_put_u32(ctx, x);
+}
+
+bare_error bare_get_i32(struct bare_reader *ctx, int32_t *x)
+{
+ return bare_get_u32(ctx, (uint32_t *) x);
+}
+
+bare_error bare_put_i64(struct bare_writer *ctx, int64_t x)
+{
+ return bare_put_u64(ctx, x);
+}
+
+bare_error bare_get_i64(struct bare_reader *ctx, int64_t *x)
+{
+ return bare_get_u64(ctx, (uint64_t *) x);
+}
+
+bare_error bare_put_f32(struct bare_writer *ctx, float x)
+{
+ uint32_t b;
+ memcpy(&b, &x, U32SZ);
+
+ return bare_put_u32(ctx, b);
+}
+
+bare_error bare_get_f32(struct bare_reader *ctx, float *x)
+{
+ return ctx->read(ctx->buffer, x, U32SZ);
+}
+
+bare_error bare_put_f64(struct bare_writer *ctx, double x)
+{
+ uint64_t b;
+ memcpy(&b, &x, U64SZ);
+
+ return bare_put_u64(ctx, b);
+}
+
+bare_error bare_get_f64(struct bare_reader *ctx, double *x)
+{
+ return ctx->read(ctx->buffer, x, U64SZ);
+}
+
+bare_error bare_put_bool(struct bare_writer *ctx, bool x)
+{
+ return bare_put_u8(ctx, (uint8_t) x);
+}
+
+bare_error bare_get_bool(struct bare_reader *ctx, bool *x)
+{
+ return bare_get_u8(ctx, (uint8_t *) x);
+}
+
+bare_error
+bare_put_fixed_data(struct bare_writer *ctx, const uint8_t *src, uint64_t sz)
+{
+ return ctx->write(ctx->buffer, (void *)src, sz);
+}
+
+bare_error
+bare_get_fixed_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz)
+{
+ return ctx->read(ctx->buffer, dst, sz);
+}
+
+bare_error
+bare_put_data(struct bare_writer *ctx, const uint8_t *src, uint64_t sz)
+{
+ bare_error err = BARE_ERROR_NONE;
+
+ err = bare_put_uint(ctx, sz);
+
+ if (err == BARE_ERROR_NONE) {
+ err = bare_put_fixed_data(ctx, src, sz);
+ }
+
+ return err;
+}
+
+bare_error bare_get_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz)
+{
+ bare_error err = BARE_ERROR_NONE;
+ uint64_t ssz = 0;
+
+ err = bare_get_uint(ctx, &ssz);
+
+ if (err == BARE_ERROR_NONE) {
+ err = ssz <= sz ? bare_get_fixed_data(ctx, dst, ssz)
+ : BARE_ERROR_BUFFER_TOO_SMALL;
+ }
+
+ return err;
+}
+
+bare_error bare_put_str(struct bare_writer *ctx, const char *src, uint64_t sz)
+{
+ return bare_put_data(ctx, (uint8_t *) src, sz);
+}
+
+bare_error bare_get_str(struct bare_reader *ctx, char *dst, uint64_t sz)
+{
+ return bare_get_data(ctx, (uint8_t *) dst, sz);
+}
diff --git a/git2d/bare.h b/git2d/bare.h
new file mode 100644
index 0000000..e813464
--- /dev/null
+++ b/git2d/bare.h
@@ -0,0 +1,72 @@
+/*-
+ * SPDX-License-Identifier: MIT
+ * SPDX-FileCopyrightText: Copyright (c) 2022 Frank Smit <https://61924.nl/>
+ */
+
+#ifndef BARE_H
+#define BARE_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef enum {
+ BARE_ERROR_NONE,
+ BARE_ERROR_WRITE_FAILED,
+ BARE_ERROR_READ_FAILED,
+ BARE_ERROR_BUFFER_TOO_SMALL,
+ BARE_ERROR_INVALID_UTF8,
+} bare_error;
+
+typedef bare_error (*bare_write_func)(void *buffer, const void *src, uint64_t sz);
+typedef bare_error (*bare_read_func)(void *buffer, void *dst, uint64_t sz);
+
+struct bare_writer {
+ void *buffer;
+ bare_write_func write;
+};
+
+struct bare_reader {
+ void *buffer;
+ bare_read_func read;
+};
+
+bare_error bare_put_uint(struct bare_writer *ctx, uint64_t x); /* varuint */
+bare_error bare_get_uint(struct bare_reader *ctx, uint64_t *x); /* varuint */
+bare_error bare_put_u8(struct bare_writer *ctx, uint8_t x);
+bare_error bare_get_u8(struct bare_reader *ctx, uint8_t *x);
+bare_error bare_put_u16(struct bare_writer *ctx, uint16_t x);
+bare_error bare_get_u16(struct bare_reader *ctx, uint16_t *x);
+bare_error bare_put_u32(struct bare_writer *ctx, uint32_t x);
+bare_error bare_get_u32(struct bare_reader *ctx, uint32_t *x);
+bare_error bare_put_u64(struct bare_writer *ctx, uint64_t x);
+bare_error bare_get_u64(struct bare_reader *ctx, uint64_t *x);
+
+bare_error bare_put_int(struct bare_writer *ctx, int64_t x); /* varint */
+bare_error bare_get_int(struct bare_reader *ctx, int64_t *x); /* varint */
+bare_error bare_put_i8(struct bare_writer *ctx, int8_t x);
+bare_error bare_get_i8(struct bare_reader *ctx, int8_t *x);
+bare_error bare_put_i16(struct bare_writer *ctx, int16_t x);
+bare_error bare_get_i16(struct bare_reader *ctx, int16_t *x);
+bare_error bare_put_i32(struct bare_writer *ctx, int32_t x);
+bare_error bare_get_i32(struct bare_reader *ctx, int32_t *x);
+bare_error bare_put_i64(struct bare_writer *ctx, int64_t x);
+bare_error bare_get_i64(struct bare_reader *ctx, int64_t *x);
+
+bare_error bare_put_f32(struct bare_writer *ctx, float x);
+bare_error bare_get_f32(struct bare_reader *ctx, float *x);
+bare_error bare_put_f64(struct bare_writer *ctx, double x);
+bare_error bare_get_f64(struct bare_reader *ctx, double *x);
+
+bare_error bare_put_bool(struct bare_writer *ctx, bool x);
+bare_error bare_get_bool(struct bare_reader *ctx, bool *x);
+
+bare_error bare_put_fixed_data(struct bare_writer *ctx, const uint8_t *src, uint64_t sz);
+bare_error bare_get_fixed_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz);
+bare_error bare_put_data(struct bare_writer *ctx, const uint8_t *src, uint64_t sz);
+bare_error bare_get_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz);
+bare_error bare_put_str(struct bare_writer *ctx, const char *src, uint64_t sz);
+bare_error bare_get_str(struct bare_reader *ctx, char *dst, uint64_t sz);
+
+/* Note that the _str implementation here does not check for UTF-8 validity. */
+
+#endif /* BARE_H */
diff --git a/git2d/cmd1.c b/git2d/cmd1.c
new file mode 100644
index 0000000..a7d8b07
--- /dev/null
+++ b/git2d/cmd1.c
@@ -0,0 +1,129 @@
+/*-
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
+ */
+
+#include "x.h"
+
+int cmd_index(git_repository *repo, struct bare_writer *writer)
+{
+ /* HEAD tree */
+
+ git_object *obj = NULL;
+ int err = git_revparse_single(&obj, repo, "HEAD^{tree}");
+ if (err != 0) {
+ bare_put_uint(writer, 4);
+ return -1;
+ }
+ git_tree *tree = (git_tree *) obj;
+
+ /* README */
+
+ git_tree_entry *entry = NULL;
+ err = git_tree_entry_bypath(&entry, tree, "README.md");
+ if (err != 0) {
+ bare_put_uint(writer, 5);
+ git_tree_free(tree);
+ return -1;
+ }
+ git_otype objtype = git_tree_entry_type(entry);
+ if (objtype != GIT_OBJECT_BLOB) {
+ bare_put_uint(writer, 6);
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+ return -1;
+ }
+ git_object *obj2 = NULL;
+ err = git_tree_entry_to_object(&obj2, repo, entry);
+ if (err != 0) {
+ bare_put_uint(writer, 7);
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+ return -1;
+ }
+ git_blob *blob = (git_blob *) obj2;
+ const void *content = git_blob_rawcontent(blob);
+ if (content == NULL) {
+ bare_put_uint(writer, 8);
+ git_blob_free(blob);
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+ return -1;
+ }
+ bare_put_uint(writer, 0);
+ bare_put_data(writer, content, git_blob_rawsize(blob));
+
+ /* Commits */
+
+ /* TODO BUG: This might be a different commit from the displayed README due to races */
+
+ git_revwalk *walker = NULL;
+ if (git_revwalk_new(&walker, repo) != 0) {
+ bare_put_uint(writer, 9);
+ git_blob_free(blob);
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+ return -1;
+ }
+
+ if (git_revwalk_push_head(walker) != 0) {
+ bare_put_uint(writer, 9);
+ git_revwalk_free(walker);
+ git_blob_free(blob);
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+ return -1;
+ }
+
+ int count = 0;
+ git_oid oid;
+ while (count < 3 && git_revwalk_next(&oid, walker) == 0) {
+ git_commit *commit = NULL;
+ if (git_commit_lookup(&commit, repo, &oid) != 0)
+ break;
+
+ const char *msg = git_commit_summary(commit);
+ const git_signature *author = git_commit_author(commit);
+
+ /* ID */
+ bare_put_data(writer, oid.id, GIT_OID_RAWSZ);
+
+ /* Title */
+ size_t msg_len = msg ? strlen(msg) : 0;
+ bare_put_data(writer, (const uint8_t *)(msg ? msg : ""),
+ msg_len);
+
+ /* Author's name */
+ const char *author_name = author ? author->name : "";
+ bare_put_data(writer, (const uint8_t *)author_name,
+ strlen(author_name));
+
+ /* Author's email */
+ const char *author_email = author ? author->email : "";
+ bare_put_data(writer, (const uint8_t *)author_email,
+ strlen(author_email));
+
+ /* Author's date */
+ /* TODO: Pass the integer instead of a string */
+ time_t time = git_commit_time(commit);
+ char timebuf[64];
+ struct tm *tm = localtime(&time);
+ if (tm)
+ strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S",
+ tm);
+ else
+ strcpy(timebuf, "unknown");
+ bare_put_data(writer, (const uint8_t *)timebuf,
+ strlen(timebuf));
+
+ git_commit_free(commit);
+ count++;
+ }
+
+ git_revwalk_free(walker);
+ git_blob_free(blob);
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+
+ return 0;
+}
diff --git a/git2d/cmd2.c b/git2d/cmd2.c
new file mode 100644
index 0000000..dd72ddb
--- /dev/null
+++ b/git2d/cmd2.c
@@ -0,0 +1,126 @@
+/*-
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
+ */
+
+#include "x.h"
+
+int
+cmd_treeraw(git_repository *repo, struct bare_reader *reader,
+ struct bare_writer *writer)
+{
+ /* Path */
+ char path[4096] = { 0 };
+ int err = bare_get_data(reader, (uint8_t *) path, sizeof(path) - 1);
+ if (err != BARE_ERROR_NONE) {
+ bare_put_uint(writer, 11);
+ return -1;
+ }
+ path[sizeof(path) - 1] = '\0';
+
+ /* HEAD^{tree} */
+ git_object *head_obj = NULL;
+ err = git_revparse_single(&head_obj, repo, "HEAD^{tree}");
+ if (err != 0) {
+ bare_put_uint(writer, 4);
+ return -1;
+ }
+ git_tree *tree = (git_tree *) head_obj;
+
+ /* Path in tree */
+ git_tree_entry *entry = NULL;
+ git_otype objtype;
+ if (strlen(path) == 0) {
+ entry = NULL;
+ objtype = GIT_OBJECT_TREE;
+ } else {
+ err = git_tree_entry_bypath(&entry, tree, path);
+ if (err != 0) {
+ bare_put_uint(writer, 3);
+ git_tree_free(tree);
+ return 0;
+ }
+ objtype = git_tree_entry_type(entry);
+ }
+
+ if (objtype == GIT_OBJECT_TREE) {
+ /* Tree */
+ git_object *tree_obj = NULL;
+ if (entry == NULL) {
+ tree_obj = (git_object *) tree;
+ } else {
+ err = git_tree_entry_to_object(&tree_obj, repo, entry);
+ if (err != 0) {
+ bare_put_uint(writer, 7);
+ goto cleanup;
+ }
+ }
+ git_tree *subtree = (git_tree *) tree_obj;
+
+ size_t count = git_tree_entrycount(subtree);
+ bare_put_uint(writer, 0);
+ bare_put_uint(writer, 1);
+ bare_put_uint(writer, count);
+ for (size_t i = 0; i < count; i++) {
+ const git_tree_entry *subentry =
+ git_tree_entry_byindex(subtree, i);
+ const char *name = git_tree_entry_name(subentry);
+ git_otype type = git_tree_entry_type(subentry);
+ uint32_t mode = git_tree_entry_filemode(subentry);
+
+ uint8_t entry_type = 0;
+ uint64_t size = 0;
+
+ if (type == GIT_OBJECT_TREE) {
+ entry_type = 1;
+ } else if (type == GIT_OBJECT_BLOB) {
+ entry_type = 2;
+
+ git_object *subobj = NULL;
+ if (git_tree_entry_to_object
+ (&subobj, repo, subentry) == 0) {
+ git_blob *b = (git_blob *) subobj;
+ size = git_blob_rawsize(b);
+ git_blob_free(b);
+ }
+ }
+
+ bare_put_uint(writer, entry_type);
+ bare_put_uint(writer, mode);
+ bare_put_uint(writer, size);
+ bare_put_data(writer, (const uint8_t *)name,
+ strlen(name));
+ }
+ if (entry != NULL) {
+ git_tree_free(subtree);
+ }
+ } else if (objtype == GIT_OBJECT_BLOB) {
+ /* Blob */
+ git_object *blob_obj = NULL;
+ err = git_tree_entry_to_object(&blob_obj, repo, entry);
+ if (err != 0) {
+ bare_put_uint(writer, 7);
+ goto cleanup;
+ }
+ git_blob *blob = (git_blob *) blob_obj;
+ const void *content = git_blob_rawcontent(blob);
+ if (content == NULL) {
+ bare_put_uint(writer, 8);
+ git_blob_free(blob);
+ goto cleanup;
+ }
+ bare_put_uint(writer, 0);
+ bare_put_uint(writer, 2);
+ bare_put_data(writer, content, git_blob_rawsize(blob));
+ git_blob_free(blob);
+ } else {
+ /* Unknown */
+ bare_put_uint(writer, -1);
+ }
+
+ cleanup:
+ if (entry != NULL)
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+ return 0;
+}
diff --git a/git2d/main.c b/git2d/main.c
new file mode 100644
index 0000000..9140c1d
--- /dev/null
+++ b/git2d/main.c
@@ -0,0 +1,82 @@
+/*-
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
+ */
+
+/*
+ * TODO: Pool repositories (and take care of thread safety)
+ * libgit2 has a nice builtin per-repo cache that we could utilize this way.
+ */
+
+#include "x.h"
+
+int main(int argc, char **argv)
+{
+ if (argc != 2) {
+ errx(1, "provide one argument: the socket path");
+ }
+
+ signal(SIGPIPE, SIG_IGN);
+
+ git_libgit2_init();
+
+ int sock;
+ if ((sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) < 0)
+ err(1, "socket");
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, argv[1]);
+
+ umask(0077);
+
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un))) {
+ if (errno == EADDRINUSE) {
+ unlink(argv[1]);
+ if (bind
+ (sock, (struct sockaddr *)&addr,
+ sizeof(struct sockaddr_un)))
+ err(1, "bind");
+ } else {
+ err(1, "bind");
+ }
+ }
+
+ listen(sock, 128);
+
+ pthread_attr_t pthread_attr;
+
+ if (pthread_attr_init(&pthread_attr) != 0)
+ err(1, "pthread_attr_init");
+
+ if (pthread_attr_setdetachstate(&pthread_attr, PTHREAD_CREATE_DETACHED) != 0)
+ err(1, "pthread_attr_setdetachstate");
+
+ for (;;) {
+ int *conn = malloc(sizeof(int));
+ if (conn == NULL) {
+ warn("malloc");
+ continue;
+ }
+
+ *conn = accept(sock, 0, 0);
+ if (*conn == -1) {
+ free(conn);
+ warn("accept");
+ continue;
+ }
+
+ pthread_t thread;
+
+ if (pthread_create (&thread, &pthread_attr, session, (void *)conn) != 0) {
+ close(*conn);
+ free(conn);
+ warn("pthread_create");
+ }
+ }
+
+ close(sock);
+
+ git_libgit2_shutdown();
+}
diff --git a/git2d/rw.c b/git2d/rw.c
new file mode 100644
index 0000000..09398c2
--- /dev/null
+++ b/git2d/rw.c
@@ -0,0 +1,34 @@
+/*-
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
+ */
+
+#include "x.h"
+
+bare_error conn_read(void *buffer, void *dst, uint64_t sz)
+{
+ conn_io_t *io = buffer;
+ ssize_t rsz = read(io->fd, dst, sz);
+ return (rsz == (ssize_t) sz) ? BARE_ERROR_NONE : BARE_ERROR_READ_FAILED;
+}
+
+bare_error conn_write(void *buffer, const void *src, uint64_t sz)
+{
+ conn_io_t *io = buffer;
+ const uint8_t *data = src;
+ uint64_t total = 0;
+
+ while (total < sz) {
+ ssize_t written = write(io->fd, data + total, sz - total);
+ if (written < 0) {
+ if (errno == EINTR)
+ continue;
+ return BARE_ERROR_WRITE_FAILED;
+ }
+ if (written == 0)
+ break;
+ total += written;
+ }
+
+ return (total == sz) ? BARE_ERROR_NONE : BARE_ERROR_WRITE_FAILED;
+}
diff --git a/git2d/session.c b/git2d/session.c
new file mode 100644
index 0000000..0a945ee
--- /dev/null
+++ b/git2d/session.c
@@ -0,0 +1,78 @@
+/*-
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
+ */
+
+#include "x.h"
+
+void *session(void *_conn)
+{
+ int conn = *(int *)_conn;
+ free((int *)_conn);
+
+ int err;
+
+ conn_io_t io = {.fd = conn };
+ struct bare_reader reader = {
+ .buffer = &io,
+ .read = conn_read,
+ };
+ struct bare_writer writer = {
+ .buffer = &io,
+ .write = conn_write,
+ };
+
+ /* Repo path */
+ char path[4096] = { 0 };
+ err = bare_get_data(&reader, (uint8_t *) path, sizeof(path) - 1);
+ if (err != BARE_ERROR_NONE) {
+ goto close;
+ }
+ path[sizeof(path) - 1] = '\0';
+
+ /* Open repo */
+ git_repository *repo = NULL;
+ err =
+ git_repository_open_ext(&repo, path,
+ GIT_REPOSITORY_OPEN_NO_SEARCH |
+ GIT_REPOSITORY_OPEN_BARE |
+ GIT_REPOSITORY_OPEN_NO_DOTGIT, NULL);
+ if (err != 0) {
+ bare_put_uint(&writer, 1);
+ goto close;
+ }
+
+ /* Command */
+ uint64_t cmd = 0;
+ err = bare_get_uint(&reader, &cmd);
+ if (err != BARE_ERROR_NONE) {
+ bare_put_uint(&writer, 2);
+ goto free_repo;
+ }
+ switch (cmd) {
+ case 1:
+ err = cmd_index(repo, &writer);
+ if (err != 0)
+ goto free_repo;
+ break;
+ case 2:
+ err = cmd_treeraw(repo, &reader, &writer);
+ if (err != 0)
+ goto free_repo;
+ break;
+ case 0:
+ bare_put_uint(&writer, 3);
+ goto free_repo;
+ default:
+ bare_put_uint(&writer, 3);
+ goto free_repo;
+ }
+
+ free_repo:
+ git_repository_free(repo);
+
+ close:
+ close(conn);
+
+ return NULL;
+}
diff --git a/git2d/x.h b/git2d/x.h
new file mode 100644
index 0000000..a6da50f
--- /dev/null
+++ b/git2d/x.h
@@ -0,0 +1,38 @@
+/*-
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
+ */
+
+#ifndef X_H
+#define X_H
+
+#include <err.h>
+#include <errno.h>
+#include <git2.h>
+#include <pthread.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bare.h"
+
+typedef struct {
+ int fd;
+} conn_io_t;
+
+
+bare_error conn_read(void *buffer, void *dst, uint64_t sz);
+bare_error conn_write(void *buffer, const void *src, uint64_t sz);
+
+void * session(void *_conn);
+
+int cmd_index(git_repository *repo, struct bare_writer *writer);
+int cmd_treeraw(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer);
+
+#endif // X_H