refactor: correct git repo design and fix some legacy problems (#38512)

This commit is contained in:
wxiaoguang
2026-07-18 21:28:14 +08:00
committed by GitHub
parent 7786df34cf
commit 66e3849a70
41 changed files with 233 additions and 233 deletions

View File

@@ -14,6 +14,7 @@ import (
"strings"
"gitea.dev/modules/git/gitcmd"
"gitea.dev/modules/globallock"
"gitea.dev/modules/log"
"gitea.dev/modules/setting"
"gitea.dev/modules/tempdir"
@@ -195,3 +196,11 @@ func runGitTests(m interface{ Run() int }) int {
}
return m.Run()
}
func LockConfigAndDo(ctx context.Context, repo RepositoryFacade, fn func(ctx context.Context) error) error {
return globallock.LockAndDo(ctx, "repo-config:"+repo.GitRepoUniqueID(), fn)
}
func LockWriteAndDo(ctx context.Context, repo RepositoryFacade, fn func(ctx context.Context) error) error {
return globallock.LockAndDo(ctx, "repo-write:"+repo.GitRepoUniqueID(), fn)
}

View File

@@ -0,0 +1,28 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitcmd
import (
"path/filepath"
"gitea.dev/modules/setting"
)
type RepositoryFacade interface {
GitRepoUniqueID() string
GitRepoLocation() string
}
func (c *Command) WithRepo(repo RepositoryFacade) *Command {
c.opts.Dir = RepoLocalPath(repo)
return c
}
func RepoLocalPath(repo RepositoryFacade) string {
repoLoc := repo.GitRepoLocation()
if filepath.IsAbs(repoLoc) {
return repoLoc
}
return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repoLoc))
}

View File

@@ -5,26 +5,26 @@
package git
import (
"bytes"
"context"
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"gitea.dev/modules/git/gitcmd"
"gitea.dev/modules/proxy"
"gitea.dev/modules/setting"
"gitea.dev/modules/util"
)
type RepositoryFacade interface {
RelativePath() string
}
type RepositoryFacade = gitcmd.RepositoryFacade
type RepositoryBase struct {
Path string
Path string // absolute path
LastCommitCache *LastCommitCache
@@ -32,40 +32,35 @@ type RepositoryBase struct {
objectFormatCache ObjectFormat
}
func prepareRepositoryBase(repoPath string) RepositoryBase {
return RepositoryBase{Path: repoPath, tagCache: newObjectCache[*Tag]()}
}
const prettyLogFormat = `--pretty=format:%H`
func (repo *Repository) ShowPrettyFormatLogToList(ctx context.Context, revisionRange string) ([]*Commit, error) {
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
logs, _, err := gitcmd.NewCommand("log").AddArguments(prettyLogFormat).
AddDynamicArguments(revisionRange).AddArguments("--").WithDir(repo.Path).
RunStdBytes(ctx)
func OpenRepository(repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return nil, err
}
return repo.parsePrettyFormatLogToList(ctx, logs)
exist, err := util.IsDir(repoPath)
if err != nil {
return nil, err
}
if !exist {
return nil, util.NewNotExistErrorf("no such file or directory")
}
gitRepo := &Repository{
RepositoryBase: RepositoryBase{Path: repoPath, tagCache: newObjectCache[*Tag]()},
}
if err = openRepositoryInternal(gitRepo); err != nil {
return nil, err
}
return gitRepo, nil
}
func (repo *Repository) parsePrettyFormatLogToList(ctx context.Context, logs []byte) ([]*Commit, error) {
var commits []*Commit
if len(logs) == 0 {
return commits, nil
func (repo *Repository) Close() error {
if repo == nil {
setting.PanicInDevOrTesting("don't close a nil repository")
return nil
}
parts := bytes.SplitSeq(logs, []byte{'\n'})
for commitID := range parts {
commit, err := repo.GetCommit(ctx, string(commitID))
if err != nil {
return nil, err
}
commits = append(commits, commit)
}
return commits, nil
repo.LastCommitCache = nil
repo.tagCache = nil
return repo.closeInternal()
}
// IsRepoURLAccessible checks if given repository URL is accessible.

View File

@@ -9,9 +9,7 @@ package git
import (
"path/filepath"
gitealog "gitea.dev/modules/log"
"gitea.dev/modules/setting"
"gitea.dev/modules/util"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/osfs"
@@ -30,25 +28,13 @@ type Repository struct {
gogitStorage *filesystem.Storage
}
func OpenRepository(repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return nil, err
}
exist, err := util.IsDir(repoPath)
if err != nil {
return nil, err
}
if !exist {
return nil, util.NewNotExistErrorf("no such file or directory")
}
fs := osfs.New(repoPath)
_, err = fs.Stat(".git")
func openRepositoryInternal(gitRepo *Repository) error {
fs := osfs.New(gitRepo.Path)
_, err := fs.Stat(".git")
if err == nil {
fs, err = fs.Chroot(".git")
if err != nil {
return nil, err
return err
}
}
// the "clone --shared" repo doesn't work well with go-git AlternativeFS, https://github.com/go-git/go-git/issues/1006
@@ -59,33 +45,23 @@ func OpenRepository(repoPath string) (*Repository, error) {
} else {
altFs = osfs.New("/")
}
storage := filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true, LargeObjectThreshold: setting.Git.LargeObjectThreshold, AlternatesFS: altFs})
gogitRepo, err := gogit.Open(storage, fs)
gitRepo.objectFormatCache = ParseGogitHash(plumbing.ZeroHash).Type()
gitRepo.gogitStorage = filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true, LargeObjectThreshold: setting.Git.LargeObjectThreshold, AlternatesFS: altFs})
gitRepo.gogitRepo, err = gogit.Open(gitRepo.gogitStorage, fs)
if err != nil {
return nil, err
_ = gitRepo.gogitStorage.Close()
return err
}
repo := &Repository{
RepositoryBase: prepareRepositoryBase(repoPath),
gogitRepo: gogitRepo,
gogitStorage: storage,
}
repo.objectFormatCache = ParseGogitHash(plumbing.ZeroHash).Type()
return repo, nil
return nil
}
// Close this repository, in particular close the underlying gogitStorage if this is not nil
func (repo *Repository) Close() error {
if repo == nil || repo.gogitStorage == nil {
func (repo *Repository) closeInternal() error {
if repo.gogitStorage == nil {
return nil
}
if err := repo.gogitStorage.Close(); err != nil {
gitealog.Error("Error closing storage: %v", err)
}
err := repo.gogitStorage.Close()
repo.gogitStorage = nil
repo.LastCommitCache = nil
repo.tagCache = nil
return nil
return err
}
// GoGitRepo gets the go-git repo representation

View File

@@ -8,12 +8,9 @@ package git
import (
"context"
"path/filepath"
"sync"
"gitea.dev/modules/log"
"gitea.dev/modules/setting"
"gitea.dev/modules/util"
)
const isGogit = false
@@ -26,19 +23,8 @@ type Repository struct {
catFileBatchInUse bool
}
func OpenRepository(repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return nil, err
}
exist, err := util.IsDir(repoPath)
if err != nil {
return nil, err
}
if !exist {
return nil, util.NewNotExistErrorf("no such file or directory")
}
return &Repository{RepositoryBase: prepareRepositoryBase(repoPath)}, nil
func openRepositoryInternal(_ *Repository) error {
return nil
}
// CatFileBatch obtains a "batch object provider" for this repository.
@@ -80,11 +66,7 @@ func (repo *Repository) CatFileBatch(ctx context.Context) (_ CatFileBatch, close
return tempBatch, tempBatch.Close, nil
}
func (repo *Repository) Close() error {
if repo == nil {
setting.PanicInDevOrTesting("don't close a nil repository")
return nil
}
func (repo *Repository) closeInternal() error {
repo.mu.Lock()
defer repo.mu.Unlock()
if repo.catFileBatchCloser != nil {
@@ -92,7 +74,5 @@ func (repo *Repository) Close() error {
repo.catFileBatchCloser = nil
repo.catFileBatchInUse = false
}
repo.LastCommitCache = nil
repo.tagCache = nil
return nil
}

View File

@@ -45,6 +45,38 @@ func (repo *Repository) GetTagCommit(ctx context.Context, name string) (*Commit,
return repo.GetCommit(ctx, RefNameFromTag(name).String())
}
const prettyLogFormat = `--pretty=format:%H`
func (repo *Repository) ShowPrettyFormatLogToList(ctx context.Context, revisionRange string) ([]*Commit, error) {
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
logs, _, err := gitcmd.NewCommand("log").AddArguments(prettyLogFormat).
AddDynamicArguments(revisionRange).AddArguments("--").WithDir(repo.Path).
RunStdBytes(ctx)
if err != nil {
return nil, err
}
return repo.parsePrettyFormatLogToList(ctx, logs)
}
func (repo *Repository) parsePrettyFormatLogToList(ctx context.Context, logs []byte) ([]*Commit, error) {
var commits []*Commit
if len(logs) == 0 {
return commits, nil
}
parts := bytes.SplitSeq(logs, []byte{'\n'})
for commitID := range parts {
commit, err := repo.GetCommit(ctx, string(commitID))
if err != nil {
return nil, err
}
commits = append(commits, commit)
}
return commits, nil
}
func (repo *Repository) getCommitByPathWithID(ctx context.Context, id ObjectID, relpath string) (*Commit, error) {
// File name starts with ':' must be escaped.
if strings.HasPrefix(relpath, ":") {