Lindenii Project Forge
Login

server

Lindenii Forge’s main backend daemon
Commit info
ID
cde5824d3ddc0cec9c3a37f146b7075c3d4ffdd7
Author
Runxi Yu <me@runxiyu.org>
Author date
Sat, 05 Apr 2025 11:19:17 +0800
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Sat, 05 Apr 2025 11:19:25 +0800
Actions
git2d: Accept an uint command
/*-
 * SPDX-License-Identifier: AGPL-3.0-only
 * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
 */

#include "x.h"

void *
session(void *_conn)
{
	int conn = *(int *)_conn;
	free((int *)_conn);

	int err;
	git_repository *repo = NULL;

	char path[4096] = {0};
	conn_io_t io = {.fd = conn };
	struct bare_reader reader = {
		.buffer = &io,
		.read = conn_read,
	};
	struct bare_writer writer = {
		.buffer = &io,
		.write = conn_write,
	};

	err = bare_get_data(&reader, (uint8_t *) path, sizeof(path) - 1);
	if (err != BARE_ERROR_NONE) {
		goto close;
	}
	path[sizeof(path) - 1] = '\0';

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

	uint64_t cmd = 0;
	err = bare_get_uint(&reader, &cmd);
	if (err != BARE_ERROR_NONE) {
		bare_put_uint(&writer, 2);
		goto free_repo;
	}
	switch (cmd) {
	case 1:
		break;
	case 0:
		bare_put_uint(&writer, 3);
		goto free_repo;
	default:
		bare_put_uint(&writer, 3);
		goto free_repo;
	}

	git_object *obj = NULL;
	err = git_revparse_single(&obj, repo, "HEAD^{tree}");
	if (err != 0) {
		bare_put_uint(&writer, 2);
		bare_put_uint(&writer, 4);
		goto free_repo;
	}
	git_tree *tree = (git_tree *) obj;

	/* README */

	git_tree_entry *entry = NULL;
	err = git_tree_entry_bypath(&entry, tree, "README.md");
	if (err != 0) {
		bare_put_uint(&writer, 3);
		bare_put_uint(&writer, 5);
		goto free_tree;
	}

	git_otype objtype = git_tree_entry_type(entry);
	if (objtype != GIT_OBJECT_BLOB) {
		bare_put_uint(&writer, 4);
		bare_put_uint(&writer, 6);
		goto free_tree_entry;
	}

	git_object *obj2 = NULL;
	err = git_tree_entry_to_object(&obj2, repo, entry);
	if (err != 0) {
		bare_put_uint(&writer, 5);
		bare_put_uint(&writer, 7);
		goto free_tree_entry;
	}

	git_blob *blob = (git_blob *) obj2;
	const void *content = git_blob_rawcontent(blob);
	if (content == NULL) {
		bare_put_uint(&writer, 6);
		bare_put_uint(&writer, 8);
		goto free_blob;
	}

	bare_put_uint(&writer, 0);
	bare_put_data(&writer, content, git_blob_rawsize(blob));

	/* Commits */

	git_revwalk *walker = NULL;
	if (git_revwalk_new(&walker, repo) != 0) {
		bare_put_uint(&writer, 7);
		bare_put_uint(&writer, 9);
		goto free_blob;
	}

	if (git_revwalk_push_head(walker) != 0) {
		bare_put_uint(&writer, 8);
		bare_put_uint(&writer, 9);
		goto free_blob;
	}

	int count = 0;
	git_oid oid;
	while (count < 3 && git_revwalk_next(&oid, walker) == 0) {
		git_commit *commit = NULL;
		if (git_commit_lookup(&commit, repo, &oid) != 0)
			break;

		const char *msg = git_commit_summary(commit);
		const git_signature *author = git_commit_author(commit);

		/* ID */
		bare_put_data(&writer, oid.id, GIT_OID_RAWSZ);

		/* Title */
		size_t msg_len = msg ? strlen(msg) : 0;
		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));

		/* Author's email */
		const char *author_email = author ? 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 */
		time_t time = git_commit_time(commit);
		char timebuf[64];
		struct tm *tm = localtime(&time);
		if (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));

		git_commit_free(commit);
		count++;
	}

	git_revwalk_free(walker);

free_blob:
	git_blob_free(blob);
free_tree_entry:
	git_tree_entry_free(entry);
free_tree:
	git_tree_free(tree);
free_repo:
	git_repository_free(repo);

close:
	close(conn);

	return NULL;
}

// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>

package main

import (
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"strings"

	"git.sr.ht/~sircmpwn/go-bare"
)

type commitDisplay struct {
	Hash    string
	Author  string
	Email   string
	Date    string
	Message string
}

// httpHandleRepoIndex provides the front page of a repo using git2d.
func httpHandleRepoIndex(w http.ResponseWriter, req *http.Request, params map[string]any) {
	repoName := params["repo_name"].(string)
	groupPath := params["group_path"].([]string)

	_, repoPath, _, _, _, _, _ := getRepoInfo(req.Context(), groupPath, repoName, "") // TODO: Don't use getRepoInfo

	var notes []string
	if strings.Contains(repoName, "\n") || sliceContainsNewlines(groupPath) {
		notes = append(notes, "Path contains newlines; HTTP Git access impossible")
	}

	conn, err := net.Dial("unix", config.Git.Socket)
	if err != nil {
		errorPage500(w, params, "git2d connection failed: "+err.Error())
		return
	}
	defer conn.Close()

	writer := bare.NewWriter(conn)
	reader := bare.NewReader(conn)

	if err := writer.WriteData([]byte(repoPath)); err != nil {
		errorPage500(w, params, "sending repo path failed: "+err.Error())
		return
	}

	reader := bare.NewReader(conn)
	if err := writer.WriteUint(1); err != nil {
		errorPage500(w, params, "sending command failed: "+err.Error())
		return
	}

	status, err := reader.ReadUint()
	if err != nil {
		errorPage500(w, params, "reading status failed: "+err.Error())
		return
	}
	if status != 0 {
		errorPage500(w, params, fmt.Sprintf("git2d error: %d", status))
		return
	}

	// README
	readmeRaw, err := reader.ReadData()
	if err != nil {
		readmeRaw = nil
	}
	readmeFilename, readmeRendered := renderReadme(readmeRaw, "README.md")

	// Commits
	var commits []commitDisplay
	for {
		id, err := reader.ReadData()
		if err != nil {
			if errors.Is(err, io.EOF) {
				break
			}
			errorPage500(w, params, "error reading commit ID: "+err.Error())
			return
		}

		title, _ := reader.ReadData()
		authorName, _ := reader.ReadData()
		authorEmail, _ := reader.ReadData()
		authorDate, _ := reader.ReadData()

		commits = append(commits, commitDisplay{
			Hash:    hex.EncodeToString(id),
			Author:  string(authorName),
			Email:   string(authorEmail),
			Date:    string(authorDate),
			Message: string(title),
		})
	}

	params["commits"] = commits
	params["readme_filename"] = readmeFilename
	params["readme"] = readmeRendered
	params["notes"] = notes

	renderTemplate(w, "repo_index", params)

	// TODO: Caching
}