Lindenii Project Forge
Warning: Due to various recent migrations, viewing non-HEAD refs may be broken.
/git2d/cmd_diff.c (raw)
/*-
* 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;
}