Lindenii Project Forge
Refactor git2d comms to ./git2c
package git2c import ( "encoding/hex" "errors" "fmt" "io" ) func (c *Client) Cmd1(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 }
package git2c import ( "errors" "fmt" "io" ) func (c *Client) Cmd2(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 i := 0; i < int(count); i++ { 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) } }
package git2c import ( "fmt" "net" "git.sr.ht/~sircmpwn/go-bare" ) type Client struct { SocketPath string conn net.Conn writer *bare.Writer reader *bare.Reader } func NewClient(socketPath string) (*Client, error) { conn, err := net.Dial("unix", socketPath) if err != nil { return nil, fmt.Errorf("git2d connection failed: %w", err) } writer := bare.NewWriter(conn) reader := bare.NewReader(conn) return &Client{ SocketPath: socketPath, conn: conn, writer: writer, reader: reader, }, nil } func (c *Client) Close() error { if c.conn != nil { return c.conn.Close() } return nil }
package git2c type Commit struct { Hash string Author string Email string Date string Message string } type FilenameContents struct { Filename string Content []byte } type TreeEntry struct { Name string Mode string Size uint64 IsFile bool IsSubtree bool }
// 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"
"go.lindenii.runxiyu.org/forge/git2c"
) 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)
client, err := git2c.NewClient(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(stringToBytes(repoPath)); err != nil { errorPage500(w, params, "sending repo path failed: "+err.Error())
errorPage500(w, params, err.Error())
return }
defer client.Close()
if err := writer.WriteUint(1); err != nil { errorPage500(w, params, "sending command failed: "+err.Error()) return } status, err := reader.ReadUint()
commits, readme, err := client.Cmd1(repoPath)
if err != nil {
errorPage500(w, params, "reading status failed: "+err.Error()) return } if status != 0 { errorPage500(w, params, fmt.Sprintf("git2d error: %d", status))
errorPage500(w, params, err.Error())
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: bytesToString(authorName), Email: bytesToString(authorEmail), Date: bytesToString(authorDate), Message: bytesToString(title), }) }
params["commits"] = commits
params["readme_filename"] = readmeFilename params["readme"] = readmeRendered
params["readme_filename"] = readme.Filename _, params["readme"] = renderReadme(readme.Content, readme.Filename)
params["notes"] = notes 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 main import (
"errors"
"fmt" "html/template"
"io" "net"
"net/http" "strings"
"git.sr.ht/~sircmpwn/go-bare"
"go.lindenii.runxiyu.org/forge/git2c"
) // httpHandleRepoRaw serves raw files, or directory listings that point to raw // files. func 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, _, _, _, _, _ := getRepoInfo(request.Context(), groupPath, repoName, "")
conn, err := net.Dial("unix", config.Git.Socket)
client, err := git2c.NewClient(config.Git.Socket)
if err != nil {
errorPage500(writer, params, "git2d connection failed: "+err.Error()) return } defer conn.Close() brWriter := bare.NewWriter(conn) brReader := bare.NewReader(conn) if err := brWriter.WriteData(stringToBytes(repoPath)); err != nil { errorPage500(writer, params, "sending repo path failed: "+err.Error()) return } if err := brWriter.WriteUint(2); err != nil { errorPage500(writer, params, "sending command failed: "+err.Error())
errorPage500(writer, params, err.Error())
return }
if err := brWriter.WriteData(stringToBytes(pathSpec)); err != nil { errorPage500(writer, params, "sending path failed: "+err.Error()) return }
defer client.Close()
status, err := brReader.ReadUint()
files, content, err := client.Cmd2(repoPath, pathSpec)
if err != nil {
errorPage500(writer, params, "reading status failed: "+err.Error())
errorPage500(writer, params, err.Error())
return }
switch status { case 0: kind, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading object kind failed: "+err.Error())
if files != nil { params["files"] = files params["readme_filename"] = "README.md" params["readme"] = template.HTML("<p>README rendering here is WIP again</p>") // TODO renderTemplate(writer, "repo_raw_dir", params) } else if content != "" { if redirectNoDir(writer, request) {
return }
switch kind { case 1: // Tree if redirectDir(writer, request) { return } count, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading entry count failed: "+err.Error()) return } files := make([]displayTreeEntry, 0, count) for range count { typeCode, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry type: "+err.Error()) return } mode, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry mode: "+err.Error()) return } size, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry size: "+err.Error()) return } name, err := brReader.ReadData() if err != nil { errorPage500(writer, params, "error reading entry name: "+err.Error()) return } files = append(files, displayTreeEntry{ Name: bytesToString(name), Mode: fmt.Sprintf("%06o", mode), Size: size, IsFile: typeCode == 2, IsSubtree: typeCode == 1, }) } params["files"] = files params["readme_filename"] = "README.md" params["readme"] = template.HTML("<p>README rendering here is WIP again</p>") // TODO renderTemplate(writer, "repo_raw_dir", params) case 2: // Blob if redirectNoDir(writer, request) { return } content, err := brReader.ReadData() if err != nil && !errors.Is(err, io.EOF) { errorPage500(writer, params, "error reading blob content: "+err.Error()) return } writer.Header().Set("Content-Type", "application/octet-stream") fmt.Fprint(writer, bytesToString(content)) default: errorPage500(writer, params, fmt.Sprintf("unknown object kind: %d", kind)) } case 3: errorPage500(writer, params, "path not found: "+pathSpec) default: errorPage500(writer, params, fmt.Sprintf("unknown status code: %d", status))
writer.Header().Set("Content-Type", "application/octet-stream") fmt.Fprint(writer, content) } else { errorPage500(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 main import (
"errors" "fmt"
"html/template"
"io" "net"
"net/http" "strings"
"git.sr.ht/~sircmpwn/go-bare"
"go.lindenii.runxiyu.org/forge/git2c"
) // 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 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, _, _, _, _, _ := getRepoInfo(request.Context(), groupPath, repoName, "")
conn, err := net.Dial("unix", config.Git.Socket)
client, err := git2c.NewClient(config.Git.Socket)
if err != nil {
errorPage500(writer, params, "git2d connection failed: "+err.Error()) return } defer conn.Close() brWriter := bare.NewWriter(conn) brReader := bare.NewReader(conn) if err := brWriter.WriteData(stringToBytes(repoPath)); err != nil { errorPage500(writer, params, "sending repo path failed: "+err.Error())
errorPage500(writer, params, err.Error())
return }
if err := brWriter.WriteUint(2); err != nil { errorPage500(writer, params, "sending command failed: "+err.Error()) return } if err := brWriter.WriteData(stringToBytes(pathSpec)); err != nil { errorPage500(writer, params, "sending path failed: "+err.Error()) return }
defer client.Close()
status, err := brReader.ReadUint()
files, content, err := client.Cmd2(repoPath, pathSpec)
if err != nil {
errorPage500(writer, params, "reading status failed: "+err.Error())
errorPage500(writer, params, err.Error())
return }
switch status { case 0: kind, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading object kind failed: "+err.Error()) return } switch kind { case 1: // Tree count, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading entry count failed: "+err.Error()) return } files := make([]displayTreeEntry, 0, count) for range count { typeCode, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry type: "+err.Error()) return } mode, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry mode: "+err.Error()) return } size, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry size: "+err.Error()) return } name, err := brReader.ReadData() if err != nil { errorPage500(writer, params, "error reading entry name: "+err.Error()) return } files = append(files, displayTreeEntry{ Name: bytesToString(name), Mode: fmt.Sprintf("%06o", mode), Size: size, IsFile: typeCode == 2, IsSubtree: typeCode == 1, }) } params["files"] = files params["readme_filename"] = "README.md" params["readme"] = template.HTML("<p>README rendering here is WIP again</p>") // TODO renderTemplate(writer, "repo_tree_dir", params) case 2: // Blob content, err := brReader.ReadData() if err != nil && !errors.Is(err, io.EOF) { errorPage500(writer, params, "error reading file content: "+err.Error()) return } rendered := renderHighlightedFile(pathSpec, bytesToString(content)) params["file_contents"] = rendered renderTemplate(writer, "repo_tree_file", params) default: errorPage500(writer, params, fmt.Sprintf("unknown kind: %d", kind)) return } case 3: errorPage500(writer, params, "path not found: "+pathSpec) return default: errorPage500(writer, params, fmt.Sprintf("unknown status code: %d", status))
if files != nil { params["files"] = files params["readme_filename"] = "README.md" params["readme"] = template.HTML("<p>README rendering here is WIP again</p>") // TODO renderTemplate(writer, "repo_tree_dir", params) } else if content != "" { rendered := renderHighlightedFile(pathSpec, content) params["file_contents"] = rendered renderTemplate(writer, "repo_tree_file", params) } else { errorPage500(writer, params, "Unknown object type, something is seriously wrong")
} }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package main import ( "embed" "html/template" "io/fs" "net/http" "github.com/tdewolff/minify/v2" "github.com/tdewolff/minify/v2/html" ) //go:embed LICENSE source.tar.gz var sourceFS embed.FS var sourceHandler = http.StripPrefix( "/-/source/", http.FileServer(http.FS(sourceFS)), ) //go:embed templates/* static/* //go:embed hookc/hookc git2d/git2d var resourcesFS embed.FS var templates *template.Template // loadTemplates minifies and loads HTML templates. func loadTemplates() (err error) { minifier := minify.New() minifierOptions := html.Minifier{ TemplateDelims: [2]string{"{{", "}}"}, KeepDefaultAttrVals: true, } //exhaustruct:ignore minifier.Add("text/html", &minifierOptions) templates = template.New("templates").Funcs(template.FuncMap{ "first_line": firstLine, "path_escape": pathEscape, "query_escape": queryEscape, "dereference_error": dereferenceOrZero[error], "minus": minus, }) err = fs.WalkDir(resourcesFS, "templates", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if !d.IsDir() { content, err := fs.ReadFile(resourcesFS, path) if err != nil { return err } minified, err := minifier.Bytes("text/html", content) if err != nil { return err } _, err = templates.Parse(bytesToString(minified)) if err != nil { return err } } return nil }) return err }
var ( staticHandler http.Handler )
var staticHandler http.Handler
// This init sets up static handlers. The resulting handlers must be // used in the HTTP router, and do nothing unless called from elsewhere. func init() { staticFS, err := fs.Sub(resourcesFS, "static") if err != nil { panic(err) } staticHandler = http.StripPrefix("/-/static/", http.FileServer(http.FS(staticFS))) }