aboutsummaryrefslogtreecommitdiff
path: root/git2d/cmd_diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'git2d/cmd_diff.c')
-rw-r--r--git2d/cmd_diff.c366
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;
+}