Files
forge/modules/gitrepo/gitrepo.go
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

129 lines
4.5 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"github.com/hanzoai/git/modules/git"
"github.com/hanzoai/git/modules/git/gitcmd"
"github.com/hanzoai/git/modules/reqctx"
"github.com/hanzoai/git/modules/setting"
"github.com/hanzoai/git/modules/util"
)
// Repository represents a git repository which stored in a disk
type Repository interface {
RelativePath() string // We don't assume how the directory structure of the repository is, so we only need the relative path
}
// repoPath resolves the Repository.RelativePath (which is a unix-style path like "username/reponame.git")
// to a local filesystem path according to setting.RepoRootPath
var repoPath = func(repo Repository) string {
return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repo.RelativePath()))
}
// OpenRepository opens the repository at the given relative path with the provided context.
func OpenRepository(ctx context.Context, repo Repository) (*git.Repository, error) {
return git.OpenRepository(ctx, repoPath(repo))
}
// contextKey is a value for use with context.WithValue.
type contextKey struct {
repoPath string
}
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
// The caller must call Closer.Close()
func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) {
reqCtx := reqctx.FromContext(ctx)
if reqCtx != nil {
gitRepo, err := RepositoryFromRequestContextOrOpen(reqCtx, repo)
return gitRepo, util.NopCloser{}, err
}
gitRepo, err := OpenRepository(ctx, repo)
return gitRepo, gitRepo, err
}
// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context.
// Caller shouldn't close the git repo manually, the git repo will be automatically closed when the request context is done.
func RepositoryFromRequestContextOrOpen(ctx reqctx.RequestContext, repo Repository) (*git.Repository, error) {
ck := contextKey{repoPath: repoPath(repo)}
if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
return gitRepo, nil
}
gitRepo, err := git.OpenRepository(ctx, ck.repoPath)
if err != nil {
return nil, err
}
ctx.AddCloser(gitRepo)
ctx.SetContextValue(ck, gitRepo)
return gitRepo, nil
}
// IsRepositoryExist returns true if the repository directory exists in the disk
func IsRepositoryExist(ctx context.Context, repo Repository) (bool, error) {
return util.IsExist(repoPath(repo))
}
// DeleteRepository deletes the repository directory from the disk, it will return
// nil if the repository does not exist.
func DeleteRepository(ctx context.Context, repo Repository) error {
return util.RemoveAll(repoPath(repo))
}
// RenameRepository renames a repository's name on disk
func RenameRepository(ctx context.Context, repo, newRepo Repository) error {
dstDir := repoPath(newRepo)
if err := os.MkdirAll(filepath.Dir(dstDir), os.ModePerm); err != nil {
return fmt.Errorf("Failed to create dir %s: %w", filepath.Dir(dstDir), err)
}
if err := util.Rename(repoPath(repo), dstDir); err != nil {
return fmt.Errorf("rename repository directory: %w", err)
}
return nil
}
func InitRepository(ctx context.Context, repo Repository, objectFormatName string) error {
return git.InitRepository(ctx, repoPath(repo), true, objectFormatName)
}
func UpdateServerInfo(ctx context.Context, repo Repository) error {
_, _, err := RunCmdBytes(ctx, repo, gitcmd.NewCommand("update-server-info"))
return err
}
func GetRepoFS(repo Repository) fs.FS {
return os.DirFS(repoPath(repo))
}
func IsRepoFileExist(ctx context.Context, repo Repository, relativeFilePath string) (bool, error) {
absoluteFilePath := filepath.Join(repoPath(repo), relativeFilePath)
return util.IsExist(absoluteFilePath)
}
func IsRepoDirExist(ctx context.Context, repo Repository, relativeDirPath string) (bool, error) {
absoluteDirPath := filepath.Join(repoPath(repo), relativeDirPath)
return util.IsDir(absoluteDirPath)
}
func RemoveRepoFileOrDir(ctx context.Context, repo Repository, relativeFileOrDirPath string) error {
absoluteFilePath := filepath.Join(repoPath(repo), relativeFileOrDirPath)
return util.Remove(absoluteFilePath)
}
func CreateRepoFile(ctx context.Context, repo Repository, relativeFilePath string) (io.WriteCloser, error) {
absoluteFilePath := filepath.Join(repoPath(repo), relativeFilePath)
if err := os.MkdirAll(filepath.Dir(absoluteFilePath), os.ModePerm); err != nil {
return nil, err
}
return os.Create(absoluteFilePath)
}