Hi… I am well aware that this diff view is very suboptimal. It will be fixed when the refactored server comes along!
git2c, git2d: Rename cmd1 and cmd2 descriptively
/*- * SPDX-License-Identifier: AGPL-3.0-only * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> */ #include "x.h" int
cmd1(git_repository *repo, struct bare_writer *writer)
cmd_index(git_repository *repo, struct bare_writer *writer)
{
/* HEAD tree */
git_object *obj = NULL;
int err = git_revparse_single(&obj, repo, "HEAD^{tree}");
if (err != 0) {
bare_put_uint(writer, 4);
return -1;
}
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, 5);
git_tree_free(tree);
return -1;
}
git_otype objtype = git_tree_entry_type(entry);
if (objtype != GIT_OBJECT_BLOB) {
bare_put_uint(writer, 6);
git_tree_entry_free(entry);
git_tree_free(tree);
return -1;
}
git_object *obj2 = NULL;
err = git_tree_entry_to_object(&obj2, repo, entry);
if (err != 0) {
bare_put_uint(writer, 7);
git_tree_entry_free(entry);
git_tree_free(tree);
return -1;
}
git_blob *blob = (git_blob *) obj2;
const void *content = git_blob_rawcontent(blob);
if (content == NULL) {
bare_put_uint(writer, 8);
git_blob_free(blob);
git_tree_entry_free(entry);
git_tree_free(tree);
return -1;
}
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, 9);
git_blob_free(blob);
git_tree_entry_free(entry);
git_tree_free(tree);
return -1;
}
if (git_revwalk_push_head(walker) != 0) {
bare_put_uint(writer, 9);
git_revwalk_free(walker);
git_blob_free(blob);
git_tree_entry_free(entry);
git_tree_free(tree);
return -1;
}
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);
git_blob_free(blob);
git_tree_entry_free(entry);
git_tree_free(tree);
return 0;
}
/*- * SPDX-License-Identifier: AGPL-3.0-only * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> */ #include "x.h" int
cmd2(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer)
cmd_treeraw(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer)
{
/* Path */
char path[4096] = {0};
int err = bare_get_data(reader, (uint8_t *)path, sizeof(path) - 1);
if (err != BARE_ERROR_NONE) {
bare_put_uint(writer, 10);
return -1;
}
path[sizeof(path) - 1] = '\0';
/* HEAD^{tree} */
git_object *head_obj = NULL;
err = git_revparse_single(&head_obj, repo, "HEAD^{tree}");
if (err != 0) {
bare_put_uint(writer, 11);
return -1;
}
git_tree *tree = (git_tree *)head_obj;
/* Path in tree */
git_tree_entry *entry = NULL;
git_otype objtype;
if (strlen(path) == 0) {
entry = NULL;
objtype = GIT_OBJECT_TREE;
} else {
err = git_tree_entry_bypath(&entry, tree, path);
if (err != 0) {
bare_put_uint(writer, 3);
git_tree_free(tree);
return 0;
}
objtype = git_tree_entry_type(entry);
}
if (objtype == GIT_OBJECT_TREE) {
/* Tree */
git_object *tree_obj = NULL;
if (entry == NULL) {
tree_obj = (git_object *)tree;
} else {
err = git_tree_entry_to_object(&tree_obj, repo, entry);
if (err != 0) {
bare_put_uint(writer, 3);
goto cleanup;
}
}
git_tree *subtree = (git_tree *)tree_obj;
size_t count = git_tree_entrycount(subtree);
bare_put_uint(writer, 0);
bare_put_uint(writer, 1);
bare_put_uint(writer, count);
for (size_t i = 0; i < count; i++) {
const git_tree_entry *subentry = git_tree_entry_byindex(subtree, i);
const char *name = git_tree_entry_name(subentry);
git_otype type = git_tree_entry_type(subentry);
uint32_t mode = git_tree_entry_filemode(subentry);
uint8_t entry_type = 0;
uint64_t size = 0;
if (type == GIT_OBJECT_TREE) {
entry_type = 1;
} else if (type == GIT_OBJECT_BLOB) {
entry_type = 2;
git_object *subobj = NULL;
if (git_tree_entry_to_object(&subobj, repo, subentry) == 0) {
git_blob *b = (git_blob *)subobj;
size = git_blob_rawsize(b);
git_blob_free(b);
}
}
bare_put_uint(writer, entry_type);
bare_put_uint(writer, mode);
bare_put_uint(writer, size);
bare_put_data(writer, (const uint8_t *)name, strlen(name));
}
if (entry != NULL) {
git_tree_free(subtree);
}
} else if (objtype == GIT_OBJECT_BLOB) {
/* Blob */
git_object *blob_obj = NULL;
err = git_tree_entry_to_object(&blob_obj, repo, entry);
if (err != 0) {
bare_put_uint(writer, 3);
goto cleanup;
}
git_blob *blob = (git_blob *)blob_obj;
const void *content = git_blob_rawcontent(blob);
if (content == NULL) {
bare_put_uint(writer, 3);
git_blob_free(blob);
goto cleanup;
}
bare_put_uint(writer, 0);
bare_put_uint(writer, 2);
bare_put_data(writer, content, git_blob_rawsize(blob));
git_blob_free(blob);
} else {
/* Unknown */
bare_put_uint(writer, 3);
}
cleanup:
if (entry != NULL)
git_tree_entry_free(entry);
git_tree_free(tree);
return 0;
}
/*-
* 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;
conn_io_t io = {.fd = conn };
struct bare_reader reader = {
.buffer = &io,
.read = conn_read,
};
struct bare_writer writer = {
.buffer = &io,
.write = conn_write,
};
/* Repo path */
char path[4096] = {0};
err = bare_get_data(&reader, (uint8_t *) path, sizeof(path) - 1);
if (err != BARE_ERROR_NONE) {
goto close;
}
path[sizeof(path) - 1] = '\0';
/* Open repo */
git_repository *repo = NULL;
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;
}
/* Command */
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:
err = cmd1(repo, &writer);
err = cmd_index(repo, &writer);
if (err != 0) goto free_repo; break; case 2:
err = cmd2(repo, &reader, &writer);
err = cmd_treeraw(repo, &reader, &writer);
if (err != 0) goto free_repo; break; case 0: bare_put_uint(&writer, 3); goto free_repo; default: bare_put_uint(&writer, 3); goto free_repo; } 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>
*/
#include <err.h>
#include <errno.h>
#include <git2.h>
#include <pthread.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "bare.h"
#ifndef X_H
#define X_H
typedef struct {
int fd;
} conn_io_t;
bare_error conn_read(void *buffer, void *dst, uint64_t sz);
bare_error conn_write(void *buffer, const void *src, uint64_t sz);
void * session(void *_conn);
int cmd1(git_repository *repo, struct bare_writer *writer); int cmd2(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer);
int cmd_index(git_repository *repo, struct bare_writer *writer); int cmd_treeraw(git_repository *repo, struct bare_reader *reader, struct bare_writer *writer);
#endif // X_H
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package git2c import ( "encoding/hex" "errors" "fmt" "io" )
func (c *Client) Cmd1(repoPath string) ([]Commit, *FilenameContents, error) {
func (c *Client) CmdIndex(repoPath string) ([]Commit, *FilenameContents, error) {
if err := c.writer.WriteData([]byte(repoPath)); err != nil {
return nil, nil, fmt.Errorf("sending repo path failed: %w", err)
}
if err := c.writer.WriteUint(1); err != nil {
return nil, nil, fmt.Errorf("sending command failed: %w", err)
}
status, err := c.reader.ReadUint()
if err != nil {
return nil, nil, fmt.Errorf("reading status failed: %w", err)
}
if status != 0 {
return nil, nil, fmt.Errorf("git2d error: %d", status)
}
// README
readmeRaw, err := c.reader.ReadData()
if err != nil {
readmeRaw = nil
}
readmeFilename := "README.md" // TODO
readme := &FilenameContents{Filename: readmeFilename, Content: readmeRaw}
// Commits
var commits []Commit
for {
id, err := c.reader.ReadData()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, nil, fmt.Errorf("reading commit ID failed: %w", err)
}
title, _ := c.reader.ReadData()
authorName, _ := c.reader.ReadData()
authorEmail, _ := c.reader.ReadData()
authorDate, _ := c.reader.ReadData()
commits = append(commits, Commit{
Hash: hex.EncodeToString(id),
Author: string(authorName),
Email: string(authorEmail),
Date: string(authorDate),
Message: string(title),
})
}
return commits, readme, nil
}
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package git2c import ( "errors" "fmt" "io" )
func (c *Client) Cmd2(repoPath, pathSpec string) ([]TreeEntry, string, error) {
func (c *Client) CmdTreeRaw(repoPath, pathSpec string) ([]TreeEntry, string, error) {
if err := c.writer.WriteData([]byte(repoPath)); err != nil {
return nil, "", fmt.Errorf("sending repo path failed: %w", err)
}
if err := c.writer.WriteUint(2); err != nil {
return nil, "", fmt.Errorf("sending command failed: %w", err)
}
if err := c.writer.WriteData([]byte(pathSpec)); err != nil {
return nil, "", fmt.Errorf("sending path failed: %w", err)
}
status, err := c.reader.ReadUint()
if err != nil {
return nil, "", fmt.Errorf("reading status failed: %w", err)
}
switch status {
case 0:
kind, err := c.reader.ReadUint()
if err != nil {
return nil, "", fmt.Errorf("reading object kind failed: %w", err)
}
switch kind {
case 1:
// Tree
count, err := c.reader.ReadUint()
if err != nil {
return nil, "", fmt.Errorf("reading entry count failed: %w", err)
}
var files []TreeEntry
for range count {
typeCode, err := c.reader.ReadUint()
if err != nil {
return nil, "", fmt.Errorf("error reading entry type: %w", err)
}
mode, err := c.reader.ReadUint()
if err != nil {
return nil, "", fmt.Errorf("error reading entry mode: %w", err)
}
size, err := c.reader.ReadUint()
if err != nil {
return nil, "", fmt.Errorf("error reading entry size: %w", err)
}
name, err := c.reader.ReadData()
if err != nil {
return nil, "", fmt.Errorf("error reading entry name: %w", err)
}
files = append(files, TreeEntry{
Name: string(name),
Mode: fmt.Sprintf("%06o", mode),
Size: size,
IsFile: typeCode == 2,
IsSubtree: typeCode == 1,
})
}
return files, "", nil
case 2:
// Blob
content, err := c.reader.ReadData()
if err != nil && !errors.Is(err, io.EOF) {
return nil, "", fmt.Errorf("error reading file content: %w", err)
}
return nil, string(content), nil
default:
return nil, "", fmt.Errorf("unknown kind: %d", kind)
}
case 3:
return nil, "", fmt.Errorf("path not found: %s", pathSpec)
default:
return nil, "", fmt.Errorf("unknown status code: %d", status)
}
}
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
package unsorted
import (
"net/http"
"go.lindenii.runxiyu.org/forge/internal/git2c"
"go.lindenii.runxiyu.org/forge/internal/render"
"go.lindenii.runxiyu.org/forge/internal/web"
)
// httpHandleRepoIndex provides the front page of a repo using git2d.
func (s *Server) httpHandleRepoIndex(w http.ResponseWriter, req *http.Request, params map[string]any) {
repoName := params["repo_name"].(string)
groupPath := params["group_path"].([]string)
_, repoPath, _, _, _, _, _ := s.getRepoInfo(req.Context(), groupPath, repoName, "") // TODO: Don't use getRepoInfo
client, err := git2c.NewClient(s.config.Git.Socket)
if err != nil {
web.ErrorPage500(s.templates, w, params, err.Error())
return
}
defer client.Close()
commits, readme, err := client.Cmd1(repoPath)
commits, readme, err := client.CmdIndex(repoPath)
if err != nil {
web.ErrorPage500(s.templates, w, params, err.Error())
return
}
params["commits"] = commits
params["readme_filename"] = readme.Filename
_, params["readme"] = render.Readme(readme.Content, readme.Filename)
s.renderTemplate(w, "repo_index", params)
// TODO: Caching
}
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
package unsorted
import (
"fmt"
"html/template"
"net/http"
"strings"
"go.lindenii.runxiyu.org/forge/internal/git2c"
"go.lindenii.runxiyu.org/forge/internal/misc"
"go.lindenii.runxiyu.org/forge/internal/web"
)
// httpHandleRepoRaw serves raw files, or directory listings that point to raw
// files.
func (s *Server) httpHandleRepoRaw(writer http.ResponseWriter, request *http.Request, params map[string]any) {
repoName := params["repo_name"].(string)
groupPath := params["group_path"].([]string)
rawPathSpec := params["rest"].(string)
pathSpec := strings.TrimSuffix(rawPathSpec, "/")
params["path_spec"] = pathSpec
_, repoPath, _, _, _, _, _ := s.getRepoInfo(request.Context(), groupPath, repoName, "")
client, err := git2c.NewClient(s.config.Git.Socket)
if err != nil {
web.ErrorPage500(s.templates, writer, params, err.Error())
return
}
defer client.Close()
files, content, err := client.Cmd2(repoPath, pathSpec)
files, content, err := client.CmdTreeRaw(repoPath, pathSpec)
if err != nil {
web.ErrorPage500(s.templates, writer, params, err.Error())
return
}
switch {
case files != nil:
params["files"] = files
params["readme_filename"] = "README.md"
params["readme"] = template.HTML("<p>README rendering here is WIP again</p>") // TODO
s.renderTemplate(writer, "repo_raw_dir", params)
case content != "":
if misc.RedirectNoDir(writer, request) {
return
}
writer.Header().Set("Content-Type", "application/octet-stream")
fmt.Fprint(writer, content)
default:
web.ErrorPage500(s.templates, writer, params, "Unknown error fetching repo raw data")
}
}
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
package unsorted
import (
"html/template"
"net/http"
"strings"
"go.lindenii.runxiyu.org/forge/internal/git2c"
"go.lindenii.runxiyu.org/forge/internal/render"
"go.lindenii.runxiyu.org/forge/internal/web"
)
// httpHandleRepoTree provides a friendly, syntax-highlighted view of
// individual files, and provides directory views that link to these files.
//
// TODO: Do not highlight files that are too large.
func (s *Server) httpHandleRepoTree(writer http.ResponseWriter, request *http.Request, params map[string]any) {
repoName := params["repo_name"].(string)
groupPath := params["group_path"].([]string)
rawPathSpec := params["rest"].(string)
pathSpec := strings.TrimSuffix(rawPathSpec, "/")
params["path_spec"] = pathSpec
_, repoPath, _, _, _, _, _ := s.getRepoInfo(request.Context(), groupPath, repoName, "")
client, err := git2c.NewClient(s.config.Git.Socket)
if err != nil {
web.ErrorPage500(s.templates, writer, params, err.Error())
return
}
defer client.Close()
files, content, err := client.Cmd2(repoPath, pathSpec)
files, content, err := client.CmdTreeRaw(repoPath, pathSpec)
if err != nil {
web.ErrorPage500(s.templates, writer, params, err.Error())
return
}
switch {
case files != nil:
params["files"] = files
params["readme_filename"] = "README.md"
params["readme"] = template.HTML("<p>README rendering here is WIP again</p>") // TODO
s.renderTemplate(writer, "repo_tree_dir", params)
case content != "":
rendered := render.Highlight(pathSpec, content)
params["file_contents"] = rendered
s.renderTemplate(writer, "repo_tree_file", params)
default:
web.ErrorPage500(s.templates, writer, params, "Unknown object type, something is seriously wrong")
}
}