Hi… I am well aware that this diff view is very suboptimal. It will be fixed when the refactored server comes along!
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)))
}