Lindenii Project Forge
Rename commitDisplay to commitDisplayOld
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package main import ( "github.com/dgraph-io/ristretto/v2" "go.lindenii.runxiyu.org/lindenii-common/clog" ) // The key is the commit ID raw hash.
var indexCommitsDisplayCache *ristretto.Cache[[]byte, []commitDisplay]
var indexCommitsDisplayCache *ristretto.Cache[[]byte, []commitDisplayOld]
func init() { var err error
indexCommitsDisplayCache, err = ristretto.NewCache(&ristretto.Config[[]byte, []commitDisplay]{
indexCommitsDisplayCache, err = ristretto.NewCache(&ristretto.Config[[]byte, []commitDisplayOld]{
NumCounters: 1e4, MaxCost: 1 << 60, BufferItems: 8192, }) if err != nil { clog.Fatal(1, "Error initializing indexCommitsCache: "+err.Error()) } }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package main import ( "context" "errors" "io" "iter" "os" "strings" "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 openRepo(ctx context.Context, groupPath []string, repoName string) (repo *git.Repository, description string, repoID int, fsPath string, err error) { err = 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 } // go-git's tree entries are not friendly for use in HTML templates. // This struct is a wrapper that is friendlier for use in templating. type displayTreeEntry struct { Name string Mode string Size int64 IsFile bool IsSubtree bool } // makeDisplayTree takes git trees of form [object.Tree] and creates a slice of // [displayTreeEntry] for easier templating. func makeDisplayTree(tree *object.Tree) (displayTree []displayTreeEntry) { for _, entry := range tree.Entries { displayEntry := displayTreeEntry{} //exhaustruct:ignore var err error var osMode os.FileMode if osMode, err = entry.Mode.ToOSFileMode(); err != nil { displayEntry.Mode = "x---------" } else { displayEntry.Mode = osMode.String() } displayEntry.IsFile = entry.Mode.IsFile() if displayEntry.Size, err = tree.Size(entry.Name); err != nil { displayEntry.Size = 0 } displayEntry.Name = strings.TrimPrefix(entry.Name, "/") displayTree = append(displayTree, displayEntry) } return displayTree } // 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 } // getRecentCommits fetches numCommits commits, starting from the headHash in a // repo. func getRecentCommits(repo *git.Repository, headHash plumbing.Hash, numCommits int) (recentCommits []*object.Commit, err error) { var commitIter object.CommitIter var thisCommit *object.Commit commitIter, err = repo.Log(&git.LogOptions{From: headHash}) //exhaustruct:ignore if err != nil { return nil, err } recentCommits = make([]*object.Commit, 0) defer commitIter.Close() if numCommits < 0 { for { thisCommit, err = commitIter.Next() if errors.Is(err, io.EOF) { return recentCommits, nil } else if err != nil { return nil, err } recentCommits = append(recentCommits, thisCommit) } } else { for range numCommits { thisCommit, err = commitIter.Next() if errors.Is(err, io.EOF) { return recentCommits, nil } else if err != nil { return nil, err } recentCommits = append(recentCommits, thisCommit) } } return recentCommits, err } // getRecentCommitsDisplay generates a slice of [commitDisplay] friendly for // use in HTML templates, consisting of numCommits commits from headhash in the // repo.
func getRecentCommitsDisplay(repo *git.Repository, headHash plumbing.Hash, numCommits int) (recentCommits []commitDisplay, err error) {
func getRecentCommitsDisplay(repo *git.Repository, headHash plumbing.Hash, numCommits int) (recentCommits []commitDisplayOld, err error) {
var commitIter object.CommitIter var thisCommit *object.Commit commitIter, err = repo.Log(&git.LogOptions{From: headHash}) //exhaustruct:ignore if err != nil { return nil, err }
recentCommits = make([]commitDisplay, 0)
recentCommits = make([]commitDisplayOld, 0)
defer commitIter.Close() if numCommits < 0 { for { thisCommit, err = commitIter.Next() if errors.Is(err, io.EOF) { return recentCommits, nil } else if err != nil { return nil, err }
recentCommits = append(recentCommits, commitDisplay{
recentCommits = append(recentCommits, commitDisplayOld{
thisCommit.Hash, thisCommit.Author, thisCommit.Committer, thisCommit.Message, thisCommit.TreeHash, }) } } else { for range numCommits { thisCommit, err = commitIter.Next() if errors.Is(err, io.EOF) { return recentCommits, nil } else if err != nil { return nil, err }
recentCommits = append(recentCommits, commitDisplay{
recentCommits = append(recentCommits, commitDisplayOld{
thisCommit.Hash, thisCommit.Author, thisCommit.Committer, thisCommit.Message, thisCommit.TreeHash, }) } } return recentCommits, err }
type commitDisplay struct {
type commitDisplayOld struct {
Hash plumbing.Hash Author object.Signature Committer object.Signature Message string TreeHash plumbing.Hash } // 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
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package main import ( "net/http" "strings" "time" "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/go-git/go-git/v5/plumbing/storer" ) // httpHandleRepoIndex provides the front page of a repo. func httpHandleRepoIndex(writer http.ResponseWriter, _ *http.Request, params map[string]any) { var repo *git.Repository var repoName string var groupPath []string var refHash plumbing.Hash var refHashSlice []byte var err error var commitObj *object.Commit var tree *object.Tree var notes []string var branches []string var branchesIter storer.ReferenceIter
var commits []commitDisplay
var commits []commitDisplayOld
repo, repoName, groupPath = params["repo"].(*git.Repository), params["repo_name"].(string), params["group_path"].([]string) if strings.Contains(repoName, "\n") || sliceContainsNewlines(groupPath) { notes = append(notes, "Path contains newlines; HTTP Git access impossible") } refHash, err = getRefHash(repo, params["ref_type"].(string), params["ref_name"].(string)) if err != nil { goto no_ref } refHashSlice = refHash[:] branchesIter, err = repo.Branches() if err == nil { _ = branchesIter.ForEach(func(branch *plumbing.Reference) error { branches = append(branches, branch.Name().Short()) return nil }) } params["branches"] = branches if value, found := indexCommitsDisplayCache.Get(refHashSlice); found { if value != nil { commits = value } else { goto readme } } else { start := time.Now() commits, err = getRecentCommitsDisplay(repo, refHash, 5) if err != nil { commits = nil } cost := time.Since(start).Nanoseconds() indexCommitsDisplayCache.Set(refHashSlice, commits, cost) if err != nil { goto readme } } params["commits"] = commits readme: if value, found := treeReadmeCache.Get(refHashSlice); found { params["files"] = value.DisplayTree params["readme_filename"] = value.ReadmeFilename params["readme"] = value.ReadmeRendered } else { start := time.Now() if commitObj, err = repo.CommitObject(refHash); err != nil { goto no_ref } if tree, err = commitObj.Tree(); err != nil { goto no_ref } displayTree := makeDisplayTree(tree) readmeFilename, readmeRendered := renderReadmeAtTree(tree) cost := time.Since(start).Nanoseconds() params["files"] = displayTree params["readme_filename"] = readmeFilename params["readme"] = readmeRendered entry := treeReadmeCacheEntry{ DisplayTree: displayTree, ReadmeFilename: readmeFilename, ReadmeRendered: readmeRendered, } treeReadmeCache.Set(refHashSlice, entry, cost) } no_ref: params["http_clone_url"] = genHTTPRemoteURL(groupPath, repoName) params["ssh_clone_url"] = genSSHRemoteURL(groupPath, repoName) params["notes"] = notes renderTemplate(writer, "repo_index", params) }