Hi… I am well aware that this diff view is very suboptimal. It will be fixed when the refactored server comes along!
oldgit: Separate some go-git stuff into here
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
package forge
package oldgit
import ( "bytes" "fmt" "strings" "time" "github.com/go-git/go-git/v5/plumbing/object" )
// fmtCommitPatch formats a commit object as if it was returned by
// FmtCommitPatch formats a commit object as if it was returned by
// git-format-patch.
func fmtCommitPatch(commit *object.Commit) (final string, err error) {
func FmtCommitPatch(commit *object.Commit) (final string, err error) {
var patch *object.Patch var buf bytes.Buffer var author object.Signature var date string var commitTitle, commitDetails string
if _, patch, err = commitToPatch(commit); err != nil {
if _, patch, err = CommitToPatch(commit); err != nil {
return "", err
}
author = commit.Author
date = author.When.Format(time.RFC1123Z)
commitTitle, commitDetails, _ = strings.Cut(commit.Message, "\n")
// This date is hardcoded in Git.
fmt.Fprintf(&buf, "From %s Mon Sep 17 00:00:00 2001\n", commit.Hash)
fmt.Fprintf(&buf, "From: %s <%s>\n", author.Name, author.Email)
fmt.Fprintf(&buf, "Date: %s\n", date)
fmt.Fprintf(&buf, "Subject: [PATCH] %s\n\n", commitTitle)
if commitDetails != "" {
commitDetails1, commitDetails2, _ := strings.Cut(commitDetails, "\n")
if strings.TrimSpace(commitDetails1) == "" {
commitDetails = commitDetails2
}
buf.WriteString(commitDetails)
buf.WriteString("\n")
}
buf.WriteString("---\n")
fmt.Fprint(&buf, patch.Stats().String())
fmt.Fprintln(&buf)
buf.WriteString(patch.String())
fmt.Fprintf(&buf, "\n-- \n2.48.1\n")
return buf.String(), nil
}
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package forge import ( "context" "errors" "io" "iter" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/jackc/pgx/v5/pgtype"
)
// openRepo opens a git repository by group and repo name.
//
// TODO: This should be deprecated in favor of doing it in the relevant
// request/router context in the future, as it cannot cover the nuance of
// fields needed.
func (s *Server) openRepo(ctx context.Context, groupPath []string, repoName string) (repo *git.Repository, description string, repoID int, fsPath string, err error) {
err = s.database.QueryRow(ctx, `
WITH RECURSIVE group_path_cte AS (
-- Start: match the first name in the path where parent_group IS NULL
SELECT
id,
parent_group,
name,
1 AS depth
FROM groups
WHERE name = ($1::text[])[1]
AND parent_group IS NULL
UNION ALL
-- Recurse: join next segment of the path
SELECT
g.id,
g.parent_group,
g.name,
group_path_cte.depth + 1
FROM groups g
JOIN group_path_cte ON g.parent_group = group_path_cte.id
WHERE g.name = ($1::text[])[group_path_cte.depth + 1]
AND group_path_cte.depth + 1 <= cardinality($1::text[])
)
SELECT
r.filesystem_path,
COALESCE(r.description, ''),
r.id
FROM group_path_cte g
JOIN repos r ON r.group_id = g.id
WHERE g.depth = cardinality($1::text[])
AND r.name = $2
`, pgtype.FlatArray[string](groupPath), repoName).Scan(&fsPath, &description, &repoID)
if err != nil {
return
}
repo, err = git.PlainOpen(fsPath)
return
}
// commitIterSeqErr creates an [iter.Seq[*object.Commit]] from an
// [object.CommitIter], and additionally returns a pointer to error.
// The pointer to error is guaranteed to be populated with either nil or the
// error returned by the commit iterator after the returned iterator is
// finished.
func commitIterSeqErr(commitIter object.CommitIter) (iter.Seq[*object.Commit], *error) {
var err error
return func(yield func(*object.Commit) bool) {
for {
commit, err2 := commitIter.Next()
if err2 != nil {
if errors.Is(err2, io.EOF) {
return
}
err = err2
return
}
if !yield(commit) {
return
}
}
}, &err
}
// commitToPatch creates an [object.Patch] from the first parent of a given
// [object.Commit].
//
// TODO: This function should be deprecated as it only diffs with the first
// parent and does not correctly handle merge commits.
func commitToPatch(commit *object.Commit) (parentCommitHash plumbing.Hash, patch *object.Patch, err error) {
var parentCommit *object.Commit
var commitTree *object.Tree
parentCommit, err = commit.Parent(0)
switch {
case errors.Is(err, object.ErrParentNotFound):
if commitTree, err = commit.Tree(); err != nil {
return
}
if patch, err = nullTree.Patch(commitTree); err != nil {
return
}
case err != nil:
return
default:
parentCommitHash = parentCommit.Hash
if patch, err = parentCommit.Patch(commit); err != nil {
return
}
}
return
}
var nullTree object.Tree //nolint:gochecknoglobals
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package forge import ( "fmt" "net/http" "strings" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/format/diff" "github.com/go-git/go-git/v5/plumbing/object" "go.lindenii.runxiyu.org/forge/internal/misc"
"go.lindenii.runxiyu.org/forge/internal/oldgit"
"go.lindenii.runxiyu.org/forge/internal/web"
)
// usableFilePatch is a [diff.FilePatch] that is structured in a way more
// friendly for use in HTML templates.
type usableFilePatch struct {
From diff.File
To diff.File
Chunks []usableChunk
}
// usableChunk is a [diff.Chunk] that is structured in a way more friendly for
// use in HTML templates.
type usableChunk struct {
Operation diff.Operation
Content string
}
func (s *Server) httpHandleRepoCommit(writer http.ResponseWriter, request *http.Request, params map[string]any) {
var repo *git.Repository
var commitIDStrSpec, commitIDStrSpecNoSuffix string
var commitID plumbing.Hash
var parentCommitHash plumbing.Hash
var commitObj *object.Commit
var commitIDStr string
var err error
var patch *object.Patch
repo, commitIDStrSpec = params["repo"].(*git.Repository), params["commit_id"].(string)
commitIDStrSpecNoSuffix = strings.TrimSuffix(commitIDStrSpec, ".patch")
commitID = plumbing.NewHash(commitIDStrSpecNoSuffix)
if commitObj, err = repo.CommitObject(commitID); err != nil {
web.ErrorPage500(s.templates, writer, params, "Error getting commit object: "+err.Error())
return
}
if commitIDStrSpecNoSuffix != commitIDStrSpec {
var patchStr string
if patchStr, err = fmtCommitPatch(commitObj); err != nil {
if patchStr, err = oldgit.FmtCommitPatch(commitObj); err != nil {
web.ErrorPage500(s.templates, writer, params, "Error formatting patch: "+err.Error())
return
}
fmt.Fprintln(writer, patchStr)
return
}
commitIDStr = commitObj.Hash.String()
if commitIDStr != commitIDStrSpec {
http.Redirect(writer, request, commitIDStr, http.StatusSeeOther)
return
}
params["commit_object"] = commitObj
params["commit_id"] = commitIDStr
parentCommitHash, patch, err = commitToPatch(commitObj)
parentCommitHash, patch, err = oldgit.CommitToPatch(commitObj)
if err != nil {
web.ErrorPage500(s.templates, writer, params, "Error getting patch from commit: "+err.Error())
return
}
params["parent_commit_hash"] = parentCommitHash.String()
params["patch"] = patch
params["file_patches"] = makeUsableFilePatches(patch)
s.renderTemplate(writer, "repo_commit", params)
}
type fakeDiffFile struct {
hash plumbing.Hash
mode filemode.FileMode
path string
}
func (f fakeDiffFile) Hash() plumbing.Hash {
return f.hash
}
func (f fakeDiffFile) Mode() filemode.FileMode {
return f.mode
}
func (f fakeDiffFile) Path() string {
return f.path
}
var nullFakeDiffFile = fakeDiffFile{ //nolint:gochecknoglobals
hash: plumbing.NewHash("0000000000000000000000000000000000000000"),
mode: misc.FirstOrPanic(filemode.New("100644")),
path: "",
}
func makeUsableFilePatches(patch diff.Patch) (usableFilePatches []usableFilePatch) {
// TODO: Remove unnecessary context
// TODO: Prepend "+"/"-"/" " instead of solely distinguishing based on color
for _, filePatch := range patch.FilePatches() {
var fromFile, toFile diff.File
var ufp usableFilePatch
chunks := []usableChunk{}
fromFile, toFile = filePatch.Files()
if fromFile == nil {
fromFile = nullFakeDiffFile
}
if toFile == nil {
toFile = nullFakeDiffFile
}
for _, chunk := range filePatch.Chunks() {
var content string
content = chunk.Content()
if len(content) > 0 && content[0] == '\n' {
content = "\n" + content
} // Horrible hack to fix how browsers newlines that immediately proceed <pre>
chunks = append(chunks, usableChunk{
Operation: chunk.Type(),
Content: content,
})
}
ufp = usableFilePatch{
Chunks: chunks,
From: fromFile,
To: toFile,
}
usableFilePatches = append(usableFilePatches, ufp)
}
return
}
package oldgit
import (
"errors"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
)
// CommitToPatch creates an [object.Patch] from the first parent of a given
// [object.Commit].
//
// TODO: This function should be deprecated as it only diffs with the first
// parent and does not correctly handle merge commits.
func CommitToPatch(commit *object.Commit) (parentCommitHash plumbing.Hash, patch *object.Patch, err error) {
var parentCommit *object.Commit
var commitTree *object.Tree
parentCommit, err = commit.Parent(0)
switch {
case errors.Is(err, object.ErrParentNotFound):
if commitTree, err = commit.Tree(); err != nil {
return
}
if patch, err = NullTree.Patch(commitTree); err != nil {
return
}
case err != nil:
return
default:
parentCommitHash = parentCommit.Hash
if patch, err = parentCommit.Patch(commit); err != nil {
return
}
}
return
}
var NullTree object.Tree //nolint:gochecknoglobals