diff options
author | Runxi Yu <me@runxiyu.org> | 2025-08-12 11:01:07 +0800 |
---|---|---|
committer | Runxi Yu <me@runxiyu.org> | 2025-09-15 15:19:12 +0800 |
commit | eb82fdb2dc0903e6125014abd64aceab42c8eb35 (patch) | |
tree | c07276ba1595c415ebc28943163d88f3e3180254 /git2d/cmd_diff.c | |
parent | Remove forge-specific functions from misc (diff) | |
download | forge-master.tar.gz forge-master.tar.zst forge-master.zip |
Diffstat (limited to 'git2d/cmd_diff.c')
-rw-r--r-- | git2d/cmd_diff.c | 366 |
1 files changed, 366 insertions, 0 deletions
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; +} |