Lindenii Project Forge
Login

server

Lindenii Forge’s main backend daemon

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;
}