diff options
Diffstat (limited to 'git2d')
-rw-r--r-- | git2d/.gitignore | 1 | ||||
-rw-r--r-- | git2d/bare.c | 17 | ||||
-rw-r--r-- | git2d/bare.h | 38 | ||||
-rw-r--r-- | git2d/cmd1.c | 15 | ||||
-rw-r--r-- | git2d/cmd2.c | 13 | ||||
-rw-r--r-- | git2d/cmd_commit.c | 188 | ||||
-rw-r--r-- | git2d/cmd_diff.c | 366 | ||||
-rw-r--r-- | git2d/cmd_init.c | 65 | ||||
-rw-r--r-- | git2d/cmd_ref.c | 113 | ||||
-rw-r--r-- | git2d/cmd_tree.c | 120 | ||||
-rw-r--r-- | git2d/main.c | 9 | ||||
-rw-r--r-- | git2d/session.c | 91 | ||||
-rw-r--r-- | git2d/x.h | 26 |
13 files changed, 987 insertions, 75 deletions
diff --git a/git2d/.gitignore b/git2d/.gitignore deleted file mode 100644 index 635d84d..0000000 --- a/git2d/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/git2d diff --git a/git2d/bare.c b/git2d/bare.c index b580980..307f3d8 100644 --- a/git2d/bare.c +++ b/git2d/bare.c @@ -169,13 +169,7 @@ bare_error bare_get_u64(struct bare_reader *ctx, uint64_t *x) 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; + | (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; @@ -257,20 +251,17 @@ 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) +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) +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 bare_put_data(struct bare_writer *ctx, const uint8_t *src, uint64_t sz) { bare_error err = BARE_ERROR_NONE; diff --git a/git2d/bare.h b/git2d/bare.h index e813464..e049dd0 100644 --- a/git2d/bare.h +++ b/git2d/bare.h @@ -17,8 +17,8 @@ typedef enum { 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); +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; @@ -30,27 +30,27 @@ struct bare_reader { 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_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_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_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_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_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_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_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_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_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_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); @@ -60,13 +60,13 @@ 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_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 */ +#endif /* BARE_H */ diff --git a/git2d/cmd1.c b/git2d/cmd1.c index a7d8b07..ec3d1ad 100644 --- a/git2d/cmd1.c +++ b/git2d/cmd1.c @@ -90,18 +90,15 @@ int cmd_index(git_repository *repo, struct bare_writer *writer) /* Title */ size_t msg_len = msg ? strlen(msg) : 0; - bare_put_data(writer, (const uint8_t *)(msg ? msg : ""), - msg_len); + 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)); + 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)); + bare_put_data(writer, (const uint8_t *)author_email, strlen(author_email)); /* Author's date */ /* TODO: Pass the integer instead of a string */ @@ -109,12 +106,10 @@ int cmd_index(git_repository *repo, struct bare_writer *writer) char timebuf[64]; struct tm *tm = localtime(&time); if (tm) - strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", - 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)); + bare_put_data(writer, (const uint8_t *)timebuf, strlen(timebuf)); git_commit_free(commit); count++; diff --git a/git2d/cmd2.c b/git2d/cmd2.c index dd72ddb..33947c6 100644 --- a/git2d/cmd2.c +++ b/git2d/cmd2.c @@ -5,9 +5,7 @@ #include "x.h" -int -cmd_treeraw(git_repository *repo, struct bare_reader *reader, - struct bare_writer *writer) +int cmd_treeraw(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) { /* Path */ char path[4096] = { 0 }; @@ -62,8 +60,7 @@ cmd_treeraw(git_repository *repo, struct bare_reader *reader, 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 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); @@ -77,8 +74,7 @@ cmd_treeraw(git_repository *repo, struct bare_reader *reader, entry_type = 2; git_object *subobj = NULL; - if (git_tree_entry_to_object - (&subobj, repo, subentry) == 0) { + 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); @@ -88,8 +84,7 @@ cmd_treeraw(git_repository *repo, struct bare_reader *reader, 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)); + bare_put_data(writer, (const uint8_t *)name, strlen(name)); } if (entry != NULL) { git_tree_free(subtree); diff --git a/git2d/cmd_commit.c b/git2d/cmd_commit.c new file mode 100644 index 0000000..3031088 --- /dev/null +++ b/git2d/cmd_commit.c @@ -0,0 +1,188 @@ +/*- + * SPDX-License-Identifier: AGPL-3.0-only + * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> + */ + +#include "x.h" + +int cmd_commit_tree_oid(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + char hex[64] = { 0 }; + if (bare_get_data(reader, (uint8_t *) hex, sizeof(hex) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + git_oid oid; + if (git_oid_fromstr(&oid, hex) != 0) { + bare_put_uint(writer, 14); + return -1; + } + git_commit *commit = NULL; + if (git_commit_lookup(&commit, repo, &oid) != 0) { + bare_put_uint(writer, 14); + return -1; + } + git_tree *tree = NULL; + if (git_commit_tree(&tree, commit) != 0) { + git_commit_free(commit); + bare_put_uint(writer, 14); + return -1; + } + const git_oid *toid = git_tree_id(tree); + bare_put_uint(writer, 0); + bare_put_data(writer, toid->id, GIT_OID_RAWSZ); + git_tree_free(tree); + git_commit_free(commit); + return 0; +} + +int cmd_commit_create(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + char treehex[64] = { 0 }; + if (bare_get_data(reader, (uint8_t *) treehex, sizeof(treehex) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + git_oid tree_oid; + if (git_oid_fromstr(&tree_oid, treehex) != 0) { + bare_put_uint(writer, 15); + return -1; + } + uint64_t pcnt = 0; + if (bare_get_uint(reader, &pcnt) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + git_commit **parents = NULL; + if (pcnt > 0) { + parents = (git_commit **) calloc(pcnt, sizeof(git_commit *)); + if (!parents) { + bare_put_uint(writer, 15); + return -1; + } + for (uint64_t i = 0; i < pcnt; i++) { + char phex[64] = { 0 }; + if (bare_get_data(reader, (uint8_t *) phex, sizeof(phex) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + goto fail; + } + git_oid poid; + if (git_oid_fromstr(&poid, phex) != 0) { + bare_put_uint(writer, 15); + goto fail; + } + if (git_commit_lookup(&parents[i], repo, &poid) != 0) { + bare_put_uint(writer, 15); + goto fail; + } + } + } + char aname[512] = { 0 }; + char aemail[512] = { 0 }; + if (bare_get_data(reader, (uint8_t *) aname, sizeof(aname) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + goto fail; + } + if (bare_get_data(reader, (uint8_t *) aemail, sizeof(aemail) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + goto fail; + } + int64_t when = 0; + int64_t tzoff = 0; + if (bare_get_i64(reader, &when) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + goto fail; + } + if (bare_get_i64(reader, &tzoff) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + goto fail; + } + char *message = NULL; + { + uint64_t msz = 0; + if (bare_get_uint(reader, &msz) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + goto fail; + } + message = (char *)malloc(msz + 1); + if (!message) { + bare_put_uint(writer, 15); + goto fail; + } + if (bare_get_fixed_data(reader, (uint8_t *) message, msz) != BARE_ERROR_NONE) { + free(message); + bare_put_uint(writer, 11); + goto fail; + } + message[msz] = '\0'; + } + git_signature *sig = NULL; + if (git_signature_new(&sig, aname, aemail, (git_time_t) when, (int)tzoff) != 0) { + free(message); + bare_put_uint(writer, 19); + goto fail; + } + git_tree *tree = NULL; + if (git_tree_lookup(&tree, repo, &tree_oid) != 0) { + git_signature_free(sig); + free(message); + bare_put_uint(writer, 19); + goto fail; + } + git_oid out; + int rc = git_commit_create(&out, repo, NULL, sig, sig, NULL, message, tree, + (int)pcnt, (const git_commit **)parents); + git_tree_free(tree); + git_signature_free(sig); + free(message); + if (rc != 0) { + bare_put_uint(writer, 19); + goto fail; + } + bare_put_uint(writer, 0); + bare_put_data(writer, out.id, GIT_OID_RAWSZ); + if (parents) { + for (uint64_t i = 0; i < pcnt; i++) + if (parents[i]) + git_commit_free(parents[i]); + free(parents); + } + return 0; + fail: + if (parents) { + for (uint64_t i = 0; i < pcnt; i++) + if (parents[i]) + git_commit_free(parents[i]); + free(parents); + } + return -1; +} + +int cmd_update_ref(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + char refname[4096] = { 0 }; + char commithex[64] = { 0 }; + if (bare_get_data(reader, (uint8_t *) refname, sizeof(refname) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + if (bare_get_data(reader, (uint8_t *) commithex, sizeof(commithex) - 1) + != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + git_oid oid; + if (git_oid_fromstr(&oid, commithex) != 0) { + bare_put_uint(writer, 18); + return -1; + } + git_reference *out = NULL; + int rc = git_reference_create(&out, repo, refname, &oid, 1, NULL); + if (rc != 0) { + bare_put_uint(writer, 18); + return -1; + } + git_reference_free(out); + bare_put_uint(writer, 0); + return 0; +} diff --git a/git2d/cmd_diff.c b/git2d/cmd_diff.c new file mode 100644 index 0000000..a7bf0b8 --- /dev/null +++ b/git2d/cmd_diff.c @@ -0,0 +1,366 @@ +/*- + * SPDX-License-Identifier: AGPL-3.0-only + * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> + */ + +#include "x.h" + +static int diff_stats_to_string(git_diff *diff, git_buf *out) +{ + git_diff_stats *stats = NULL; + if (git_diff_get_stats(&stats, diff) != 0) { + return -1; + } + int rc = git_diff_stats_to_buf(out, stats, GIT_DIFF_STATS_FULL, 80); + git_diff_stats_free(stats); + return rc; +} + +static void split_message(const char *message, char **title_out, char **body_out) +{ + *title_out = NULL; + *body_out = NULL; + if (!message) + return; + const char *nl = strchr(message, '\n'); + if (!nl) { + *title_out = strdup(message); + *body_out = strdup(""); + return; + } + size_t title_len = (size_t)(nl - message); + *title_out = (char *)malloc(title_len + 1); + if (*title_out) { + memcpy(*title_out, message, title_len); + (*title_out)[title_len] = '\0'; + } + const char *rest = nl + 1; + if (*rest == '\n') + rest++; + *body_out = strdup(rest); +} + +int cmd_format_patch(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + char hex[64] = { 0 }; + if (bare_get_data(reader, (uint8_t *) hex, sizeof(hex) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + git_oid oid; + if (git_oid_fromstr(&oid, hex) != 0) { + bare_put_uint(writer, 14); + return -1; + } + + git_commit *commit = NULL; + if (git_commit_lookup(&commit, repo, &oid) != 0) { + bare_put_uint(writer, 14); + return -1; + } + + git_tree *tree = NULL; + if (git_commit_tree(&tree, commit) != 0) { + git_commit_free(commit); + bare_put_uint(writer, 14); + return -1; + } + + git_diff *diff = NULL; + if (git_commit_parentcount(commit) == 0) { + if (git_diff_tree_to_tree(&diff, repo, NULL, tree, NULL) != 0) { + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + } else { + git_commit *parent = NULL; + git_tree *ptree = NULL; + if (git_commit_parent(&parent, commit, 0) != 0 || git_commit_tree(&ptree, parent) != 0) { + if (parent) + git_commit_free(parent); + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + if (git_diff_tree_to_tree(&diff, repo, ptree, tree, NULL) != 0) { + git_tree_free(ptree); + git_commit_free(parent); + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + git_tree_free(ptree); + git_commit_free(parent); + } + + git_buf stats = { 0 }; + if (diff_stats_to_string(diff, &stats) != 0) { + git_diff_free(diff); + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + + git_buf patch = { 0 }; + if (git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH) != 0) { + git_buf_dispose(&stats); + git_diff_free(diff); + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + + const git_signature *author = git_commit_author(commit); + char *title = NULL, *body = NULL; + split_message(git_commit_message(commit), &title, &body); + + char header[2048]; + char timebuf[64]; + { + time_t t = git_commit_time(commit); + struct tm *tm = localtime(&t); + if (tm) + strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %z", tm); + else + strcpy(timebuf, "unknown"); + } + snprintf(header, sizeof(header), "From %s Mon Sep 17 00:00:00 2001\nFrom: %s <%s>\nDate: %s\nSubject: [PATCH] %s\n\n", git_oid_tostr_s(&oid), author && author->name ? author->name : "", author && author->email ? author->email : "", timebuf, title ? title : ""); + + const char *trailer = "\n-- \n2.48.1\n"; + size_t header_len = strlen(header); + size_t body_len = body ? strlen(body) : 0; + size_t trailer_len = strlen(trailer); + size_t total = header_len + body_len + (body_len ? 1 : 0) + 4 + stats.size + 1 + patch.size + trailer_len; + + uint8_t *buf = (uint8_t *) malloc(total); + if (!buf) { + free(title); + free(body); + git_buf_dispose(&patch); + git_buf_dispose(&stats); + git_diff_free(diff); + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + size_t off = 0; + memcpy(buf + off, header, header_len); + off += header_len; + if (body_len) { + memcpy(buf + off, body, body_len); + off += body_len; + buf[off++] = '\n'; + } + memcpy(buf + off, "---\n", 4); + off += 4; + memcpy(buf + off, stats.ptr, stats.size); + off += stats.size; + buf[off++] = '\n'; + memcpy(buf + off, patch.ptr, patch.size); + off += patch.size; + memcpy(buf + off, trailer, trailer_len); + off += trailer_len; + + bare_put_uint(writer, 0); + bare_put_data(writer, buf, off); + + free(buf); + free(title); + free(body); + git_buf_dispose(&patch); + git_buf_dispose(&stats); + git_diff_free(diff); + git_tree_free(tree); + git_commit_free(commit); + return 0; +} + +int cmd_commit_diff(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + char hex[64] = { 0 }; + if (bare_get_data(reader, (uint8_t *) hex, sizeof(hex) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + git_oid oid; + if (git_oid_fromstr(&oid, hex) != 0) { + bare_put_uint(writer, 14); + return -1; + } + + git_commit *commit = NULL; + if (git_commit_lookup(&commit, repo, &oid) != 0) { + bare_put_uint(writer, 14); + return -1; + } + + git_tree *tree = NULL; + if (git_commit_tree(&tree, commit) != 0) { + git_commit_free(commit); + bare_put_uint(writer, 14); + return -1; + } + + git_diff *diff = NULL; + git_oid parent_oid = { 0 }; + if (git_commit_parentcount(commit) == 0) { + if (git_diff_tree_to_tree(&diff, repo, NULL, tree, NULL) != 0) { + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + } else { + git_commit *parent = NULL; + git_tree *ptree = NULL; + if (git_commit_parent(&parent, commit, 0) != 0 || git_commit_tree(&ptree, parent) != 0) { + if (parent) + git_commit_free(parent); + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + git_oid_cpy(&parent_oid, git_commit_id(parent)); + if (git_diff_tree_to_tree(&diff, repo, ptree, tree, NULL) != 0) { + git_tree_free(ptree); + git_commit_free(parent); + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + git_tree_free(ptree); + git_commit_free(parent); + } + + git_buf stats = { 0 }; + if (diff_stats_to_string(diff, &stats) != 0) { + git_diff_free(diff); + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + git_buf patch = { 0 }; + if (git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH) != 0) { + git_buf_dispose(&stats); + git_diff_free(diff); + git_tree_free(tree); + git_commit_free(commit); + bare_put_uint(writer, 15); + return -1; + } + + bare_put_uint(writer, 0); + bare_put_data(writer, parent_oid.id, GIT_OID_RAWSZ); + bare_put_data(writer, (const uint8_t *)stats.ptr, stats.size); + bare_put_data(writer, (const uint8_t *)patch.ptr, patch.size); + + git_buf_dispose(&patch); + git_buf_dispose(&stats); + git_diff_free(diff); + git_tree_free(tree); + git_commit_free(commit); + return 0; +} + +int cmd_merge_base(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + char hex1[64] = { 0 }; + char hex2[64] = { 0 }; + if (bare_get_data(reader, (uint8_t *) hex1, sizeof(hex1) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + if (bare_get_data(reader, (uint8_t *) hex2, sizeof(hex2) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + git_oid a, b, out; + if (git_oid_fromstr(&a, hex1) != 0 || git_oid_fromstr(&b, hex2) != 0) { + bare_put_uint(writer, 17); + return -1; + } + int rc = git_merge_base(&out, repo, &a, &b); + if (rc == GIT_ENOTFOUND) { + bare_put_uint(writer, 16); + return -1; + } + if (rc != 0) { + bare_put_uint(writer, 17); + return -1; + } + bare_put_uint(writer, 0); + bare_put_data(writer, out.id, GIT_OID_RAWSZ); + return 0; +} + +int cmd_log(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + char spec[4096] = { 0 }; + uint64_t limit = 0; + if (bare_get_data(reader, (uint8_t *) spec, sizeof(spec) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + if (bare_get_uint(reader, &limit) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + + git_object *obj = NULL; + if (spec[0] == '\0') + strcpy(spec, "HEAD"); + if (git_revparse_single(&obj, repo, spec) != 0) { + bare_put_uint(writer, 4); + return -1; + } + git_commit *start = (git_commit *) obj; + + git_revwalk *walk = NULL; + if (git_revwalk_new(&walk, repo) != 0) { + git_commit_free(start); + bare_put_uint(writer, 9); + return -1; + } + git_revwalk_sorting(walk, GIT_SORT_TIME); + git_revwalk_push(walk, git_commit_id(start)); + git_commit_free(start); + + bare_put_uint(writer, 0); + git_oid oid; + uint64_t count = 0; + while ((limit == 0 || count < limit) + && git_revwalk_next(&oid, walk) == 0) { + git_commit *c = NULL; + if (git_commit_lookup(&c, repo, &oid) != 0) + break; + const char *msg = git_commit_summary(c); + const git_signature *author = git_commit_author(c); + time_t t = git_commit_time(c); + char timebuf[64]; + struct tm *tm = localtime(&t); + if (tm) + strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm); + else + strcpy(timebuf, "unknown"); + + bare_put_data(writer, oid.id, GIT_OID_RAWSZ); + bare_put_data(writer, (const uint8_t *)(msg ? msg : ""), msg ? strlen(msg) : 0); + bare_put_data(writer, (const uint8_t *)(author && author->name ? author->name : ""), author && author->name ? strlen(author->name) : 0); + bare_put_data(writer, (const uint8_t *)(author && author->email ? author->email : ""), author && author->email ? strlen(author->email) : 0); + bare_put_data(writer, (const uint8_t *)timebuf, strlen(timebuf)); + git_commit_free(c); + count++; + } + git_revwalk_free(walk); + return 0; +} diff --git a/git2d/cmd_init.c b/git2d/cmd_init.c new file mode 100644 index 0000000..962d229 --- /dev/null +++ b/git2d/cmd_init.c @@ -0,0 +1,65 @@ +/*- + * SPDX-License-Identifier: AGPL-3.0-only + * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> + */ + +#include "x.h" + +int cmd_init_repo(const char *path, struct bare_reader *reader, struct bare_writer *writer) +{ + char hooks[4096] = { 0 }; + if (bare_get_data(reader, (uint8_t *) hooks, sizeof(hooks) - 1) != BARE_ERROR_NONE) { + fprintf(stderr, "init_repo: protocol error reading hooks for path '%s'\n", path); + bare_put_uint(writer, 11); + return -1; + } + + fprintf(stderr, "init_repo: starting for path='%s' hooks='%s'\n", path, hooks); + + if (mkdir(path, 0700) != 0 && errno != EEXIST) { + fprintf(stderr, "init_repo: mkdir failed for '%s': %s\n", path, strerror(errno)); + bare_put_uint(writer, 24); + return -1; + } + + git_repository *repo = NULL; + git_repository_init_options opts; + git_repository_init_options_init(&opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION); + opts.flags = GIT_REPOSITORY_INIT_BARE; + if (git_repository_init_ext(&repo, path, &opts) != 0) { + const git_error *ge = git_error_last(); + fprintf(stderr, "init_repo: git_repository_init_ext failed: %s (klass=%d)\n", ge && ge->message ? ge->message : "(no message)", ge ? ge->klass : 0); + bare_put_uint(writer, 20); + return -1; + } + git_config *cfg = NULL; + if (git_repository_config(&cfg, repo) != 0) { + git_repository_free(repo); + const git_error *ge = git_error_last(); + fprintf(stderr, "init_repo: open config failed: %s (klass=%d)\n", ge && ge->message ? ge->message : "(no message)", ge ? ge->klass : 0); + bare_put_uint(writer, 21); + return -1; + } + if (git_config_set_string(cfg, "core.hooksPath", hooks) != 0) { + git_config_free(cfg); + git_repository_free(repo); + const git_error *ge = git_error_last(); + fprintf(stderr, "init_repo: set hooksPath failed: %s (klass=%d) hooks='%s'\n", ge && ge->message ? ge->message : "(no message)", ge ? ge->klass : 0, hooks); + bare_put_uint(writer, 22); + return -1; + } + if (git_config_set_bool(cfg, "receive.advertisePushOptions", 1) != 0) { + git_config_free(cfg); + git_repository_free(repo); + const git_error *ge = git_error_last(); + fprintf(stderr, "init_repo: set advertisePushOptions failed: %s (klass=%d)\n", ge && ge->message ? ge->message : "(no message)", ge ? ge->klass : 0); + bare_put_uint(writer, 23); + return -1; + } + git_config_free(cfg); + + git_repository_free(repo); + fprintf(stderr, "init_repo: success for path='%s'\n", path); + bare_put_uint(writer, 0); + return 0; +} diff --git a/git2d/cmd_ref.c b/git2d/cmd_ref.c new file mode 100644 index 0000000..f4bae4a --- /dev/null +++ b/git2d/cmd_ref.c @@ -0,0 +1,113 @@ +/*- + * SPDX-License-Identifier: AGPL-3.0-only + * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> + */ + +#include "x.h" + +static int write_oid(struct bare_writer *writer, const git_oid *oid) +{ + return bare_put_data(writer, oid->id, GIT_OID_RAWSZ) == BARE_ERROR_NONE ? 0 : -1; +} + +int cmd_resolve_ref(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + char type[32] = { 0 }; + char name[4096] = { 0 }; + if (bare_get_data(reader, (uint8_t *) type, sizeof(type) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + if (bare_get_data(reader, (uint8_t *) name, sizeof(name) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + + git_oid oid = { 0 }; + int err = 0; + + if (type[0] == '\0') { + git_object *obj = NULL; + err = git_revparse_single(&obj, repo, "HEAD^{commit}"); + if (err != 0) { + bare_put_uint(writer, 12); + return -1; + } + git_commit *c = (git_commit *) obj; + git_oid_cpy(&oid, git_commit_id(c)); + git_commit_free(c); + } else if (strcmp(type, "commit") == 0) { + err = git_oid_fromstr(&oid, name); + if (err != 0) { + bare_put_uint(writer, 12); + return -1; + } + } else if (strcmp(type, "branch") == 0) { + char fullref[4608]; + snprintf(fullref, sizeof(fullref), "refs/heads/%s", name); + git_object *obj = NULL; + err = git_revparse_single(&obj, repo, fullref); + if (err != 0) { + bare_put_uint(writer, 12); + return -1; + } + git_commit *c = (git_commit *) obj; + git_oid_cpy(&oid, git_commit_id(c)); + git_commit_free(c); + } else if (strcmp(type, "tag") == 0) { + char spec[4608]; + snprintf(spec, sizeof(spec), "refs/tags/%s^{commit}", name); + git_object *obj = NULL; + err = git_revparse_single(&obj, repo, spec); + if (err != 0) { + bare_put_uint(writer, 12); + return -1; + } + git_commit *c = (git_commit *) obj; + git_oid_cpy(&oid, git_commit_id(c)); + git_commit_free(c); + } else { + bare_put_uint(writer, 12); + return -1; + } + + bare_put_uint(writer, 0); + return write_oid(writer, &oid); +} + +int cmd_list_branches(git_repository *repo, struct bare_writer *writer) +{ + git_branch_iterator *it = NULL; + int err = git_branch_iterator_new(&it, repo, GIT_BRANCH_LOCAL); + if (err != 0) { + bare_put_uint(writer, 13); + return -1; + } + size_t count = 0; + git_reference *ref; + git_branch_t type; + while (git_branch_next(&ref, &type, it) == 0) { + count++; + git_reference_free(ref); + } + git_branch_iterator_free(it); + + err = git_branch_iterator_new(&it, repo, GIT_BRANCH_LOCAL); + if (err != 0) { + bare_put_uint(writer, 13); + return -1; + } + + bare_put_uint(writer, 0); + bare_put_uint(writer, count); + while (git_branch_next(&ref, &type, it) == 0) { + const char *name = NULL; + git_branch_name(&name, ref); + if (name == NULL) + name = ""; + bare_put_data(writer, (const uint8_t *)name, strlen(name)); + git_reference_free(ref); + } + git_branch_iterator_free(it); + return 0; +} diff --git a/git2d/cmd_tree.c b/git2d/cmd_tree.c new file mode 100644 index 0000000..d18e817 --- /dev/null +++ b/git2d/cmd_tree.c @@ -0,0 +1,120 @@ +/*- + * SPDX-License-Identifier: AGPL-3.0-only + * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> + */ + +#include "x.h" + +int cmd_tree_list_by_oid(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + char hex[64] = { 0 }; + if (bare_get_data(reader, (uint8_t *) hex, sizeof(hex) - 1) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + git_oid oid; + if (git_oid_fromstr(&oid, hex) != 0) { + bare_put_uint(writer, 4); + return -1; + } + git_tree *tree = NULL; + if (git_tree_lookup(&tree, repo, &oid) != 0) { + bare_put_uint(writer, 4); + return -1; + } + size_t count = git_tree_entrycount(tree); + bare_put_uint(writer, 0); + bare_put_uint(writer, count); + for (size_t i = 0; i < count; i++) { + const git_tree_entry *e = git_tree_entry_byindex(tree, i); + const char *name = git_tree_entry_name(e); + uint32_t mode = git_tree_entry_filemode(e); + const git_oid *id = git_tree_entry_id(e); + bare_put_uint(writer, mode); + bare_put_data(writer, (const uint8_t *)name, strlen(name)); + bare_put_data(writer, id->id, GIT_OID_RAWSZ); + } + git_tree_free(tree); + return 0; +} + +int cmd_write_tree(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + uint64_t count = 0; + if (bare_get_uint(reader, &count) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + git_treebuilder *bld = NULL; + if (git_treebuilder_new(&bld, repo, NULL) != 0) { + bare_put_uint(writer, 15); + return -1; + } + for (uint64_t i = 0; i < count; i++) { + uint64_t mode = 0; + if (bare_get_uint(reader, &mode) != BARE_ERROR_NONE) { + git_treebuilder_free(bld); + bare_put_uint(writer, 11); + return -1; + } + char name[4096] = { 0 }; + if (bare_get_data(reader, (uint8_t *) name, sizeof(name) - 1) != BARE_ERROR_NONE) { + git_treebuilder_free(bld); + bare_put_uint(writer, 11); + return -1; + } + uint8_t idraw[GIT_OID_RAWSZ] = { 0 }; + if (bare_get_fixed_data(reader, idraw, GIT_OID_RAWSZ) != BARE_ERROR_NONE) { + git_treebuilder_free(bld); + bare_put_uint(writer, 11); + return -1; + } + git_oid id; + memcpy(id.id, idraw, GIT_OID_RAWSZ); + git_filemode_t fm = (git_filemode_t) mode; + if (git_treebuilder_insert(NULL, bld, name, &id, fm) != 0) { + git_treebuilder_free(bld); + bare_put_uint(writer, 15); + return -1; + } + } + git_oid out; + if (git_treebuilder_write(&out, bld) != 0) { + git_treebuilder_free(bld); + bare_put_uint(writer, 15); + return -1; + } + git_treebuilder_free(bld); + bare_put_uint(writer, 0); + bare_put_data(writer, out.id, GIT_OID_RAWSZ); + return 0; +} + +int cmd_blob_write(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer) +{ + uint64_t sz = 0; + if (bare_get_uint(reader, &sz) != BARE_ERROR_NONE) { + bare_put_uint(writer, 11); + return -1; + } + uint8_t *data = (uint8_t *) malloc(sz); + if (!data) { + bare_put_uint(writer, 15); + return -1; + } + if (bare_get_fixed_data(reader, data, sz) != BARE_ERROR_NONE) { + free(data); + bare_put_uint(writer, 11); + return -1; + } + git_oid oid; + if (git_blob_create_frombuffer(&oid, repo, data, sz) != 0) { + free(data); + bare_put_uint(writer, 15); + return -1; + } + free(data); + bare_put_uint(writer, 0); + bare_put_data(writer, oid.id, GIT_OID_RAWSZ); + return 0; +} diff --git a/git2d/main.c b/git2d/main.c index 9140c1d..8518960 100644 --- a/git2d/main.c +++ b/git2d/main.c @@ -34,9 +34,7 @@ int main(int argc, char **argv) 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))) + if (bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un))) err(1, "bind"); } else { err(1, "bind"); @@ -50,7 +48,8 @@ int main(int argc, char **argv) if (pthread_attr_init(&pthread_attr) != 0) err(1, "pthread_attr_init"); - if (pthread_attr_setdetachstate(&pthread_attr, PTHREAD_CREATE_DETACHED) != 0) + if (pthread_attr_setdetachstate(&pthread_attr, PTHREAD_CREATE_DETACHED) + != 0) err(1, "pthread_attr_setdetachstate"); for (;;) { @@ -69,7 +68,7 @@ int main(int argc, char **argv) pthread_t thread; - if (pthread_create (&thread, &pthread_attr, session, (void *)conn) != 0) { + if (pthread_create(&thread, &pthread_attr, session, (void *)conn) != 0) { close(*conn); free(conn); warn("pthread_create"); diff --git a/git2d/session.c b/git2d/session.c index 0a945ee..b5691d3 100644 --- a/git2d/session.c +++ b/git2d/session.c @@ -29,25 +29,30 @@ void *session(void *_conn) 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; - } + fprintf(stderr, "session: path='%s'\n", path); /* Command */ uint64_t cmd = 0; err = bare_get_uint(&reader, &cmd); if (err != BARE_ERROR_NONE) { bare_put_uint(&writer, 2); - goto free_repo; + goto close; + } + fprintf(stderr, "session: cmd=%llu\n", (unsigned long long)cmd); + + /* Repo init does not require opening an existing repo so let's just do it here */ + if (cmd == 15) { + fprintf(stderr, "session: handling init for '%s'\n", path); + if (cmd_init_repo(path, &reader, &writer) != 0) { + } + goto close; + } + + 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; } switch (cmd) { case 1: @@ -60,6 +65,66 @@ void *session(void *_conn) if (err != 0) goto free_repo; break; + case 3: + err = cmd_resolve_ref(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 4: + err = cmd_list_branches(repo, &writer); + if (err != 0) + goto free_repo; + break; + case 5: + err = cmd_format_patch(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 6: + err = cmd_commit_diff(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 7: + err = cmd_merge_base(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 8: + err = cmd_log(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 9: + err = cmd_tree_list_by_oid(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 10: + err = cmd_write_tree(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 11: + err = cmd_blob_write(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 12: + err = cmd_commit_tree_oid(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 13: + err = cmd_commit_create(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; + case 14: + err = cmd_update_ref(repo, &reader, &writer); + if (err != 0) + goto free_repo; + break; case 0: bare_put_uint(&writer, 3); goto free_repo; @@ -26,13 +26,29 @@ 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); +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); + +int cmd_resolve_ref(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); +int cmd_list_branches(git_repository * repo, struct bare_writer *writer); +int cmd_format_patch(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); +int cmd_commit_diff(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); +int cmd_merge_base(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); +int cmd_log(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); + +int cmd_tree_list_by_oid(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); +int cmd_write_tree(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); +int cmd_blob_write(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); + +int cmd_commit_tree_oid(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); +int cmd_commit_create(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); +int cmd_update_ref(git_repository * repo, struct bare_reader *reader, struct bare_writer *writer); -int cmd_index(git_repository *repo, struct bare_writer *writer); -int cmd_treeraw(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer); +int cmd_init_repo(const char *path, struct bare_reader *reader, struct bare_writer *writer); -#endif // X_H +#endif // X_H |