mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-02 09:17:10 +02:00
refactor: correct git repo design and fix some legacy problems (#38512)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
+28
-33
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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, ":") {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
@@ -18,24 +17,22 @@ import (
|
||||
)
|
||||
|
||||
// CreateArchive create archive content to the target path
|
||||
func CreateArchive(ctx context.Context, repo Repository, format string, target io.Writer, usePrefix bool, commitID string, paths []string) error {
|
||||
func CreateArchive(ctx context.Context, repo Repository, repoName, format string, target io.Writer, commitID string, paths []string) error {
|
||||
if format == "unknown" {
|
||||
return fmt.Errorf("unknown format: %v", format)
|
||||
}
|
||||
|
||||
cmd := gitcmd.NewCommand("archive")
|
||||
if usePrefix {
|
||||
cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.RelativePath(), ".git"))+"/")
|
||||
if setting.Repository.PrefixArchiveFiles {
|
||||
cmd.AddOptionFormat("--prefix=%s", strings.ToLower(repoName)+"/")
|
||||
}
|
||||
cmd.AddOptionFormat("--format=%s", format)
|
||||
cmd.AddDynamicArguments(commitID)
|
||||
|
||||
paths = slices.Clone(paths)
|
||||
for i := range paths {
|
||||
// although "git archive" already ensures the paths won't go outside the repo, we still clean them here for safety
|
||||
paths[i] = path.Clean(paths[i])
|
||||
cmd.AddDynamicArguments(path.Clean(paths[i]))
|
||||
}
|
||||
cmd.AddDynamicArguments(paths...)
|
||||
return RunCmdWithStderr(ctx, repo, cmd.WithStdoutCopy(target))
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestReadingBlameOutputSha256(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("Without .git-blame-ignore-revs", func(t *testing.T) {
|
||||
storage := &mockRepository{path: "repo5_pulls_sha256"}
|
||||
storage := mockRepository("repo5_pulls_sha256")
|
||||
repo, err := OpenRepository(storage)
|
||||
assert.NoError(t, err)
|
||||
defer repo.Close()
|
||||
@@ -70,7 +70,7 @@ func TestReadingBlameOutputSha256(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("With .git-blame-ignore-revs", func(t *testing.T) {
|
||||
storage := &mockRepository{path: "repo6_blame_sha256"}
|
||||
storage := mockRepository("repo6_blame_sha256")
|
||||
repo, err := OpenRepository(storage)
|
||||
assert.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestReadingBlameOutput(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
t.Run("Without .git-blame-ignore-revs", func(t *testing.T) {
|
||||
storage := &mockRepository{path: "repo5_pulls"}
|
||||
storage := mockRepository("repo5_pulls")
|
||||
repo, err := OpenRepository(storage)
|
||||
assert.NoError(t, err)
|
||||
defer repo.Close()
|
||||
@@ -64,7 +64,7 @@ func TestReadingBlameOutput(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("With .git-blame-ignore-revs", func(t *testing.T) {
|
||||
storage := &mockRepository{path: "repo6_blame"}
|
||||
storage := mockRepository("repo6_blame")
|
||||
repo, err := OpenRepository(storage)
|
||||
assert.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
@@ -10,17 +10,17 @@ import (
|
||||
)
|
||||
|
||||
func RunCmd(ctx context.Context, repo Repository, cmd *gitcmd.Command) error {
|
||||
return cmd.WithDir(repoPath(repo)).WithParentCallerInfo().Run(ctx)
|
||||
return cmd.WithRepo(repo).WithParentCallerInfo().Run(ctx)
|
||||
}
|
||||
|
||||
func RunCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command) (string, string, gitcmd.RunStdError) {
|
||||
return cmd.WithDir(repoPath(repo)).WithParentCallerInfo().RunStdString(ctx)
|
||||
return cmd.WithRepo(repo).WithParentCallerInfo().RunStdString(ctx)
|
||||
}
|
||||
|
||||
func RunCmdBytes(ctx context.Context, repo Repository, cmd *gitcmd.Command) ([]byte, []byte, gitcmd.RunStdError) {
|
||||
return cmd.WithDir(repoPath(repo)).WithParentCallerInfo().RunStdBytes(ctx)
|
||||
return cmd.WithRepo(repo).WithParentCallerInfo().RunStdBytes(ctx)
|
||||
}
|
||||
|
||||
func RunCmdWithStderr(ctx context.Context, repo Repository, cmd *gitcmd.Command) gitcmd.RunStdError {
|
||||
return cmd.WithDir(repoPath(repo)).WithParentCallerInfo().RunWithStderr(ctx)
|
||||
return cmd.WithRepo(repo).WithParentCallerInfo().RunWithStderr(ctx)
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ func TestParseCommitFileStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetCommitFileStatusMerges(t *testing.T) {
|
||||
bareRepo6 := &mockRepository{path: "repo6_merge"}
|
||||
bareRepo6 := mockRepository("repo6_merge")
|
||||
|
||||
commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6, "022f4ce6214973e018f02bf363bf8a2e3691f699")
|
||||
assert.NoError(t, err)
|
||||
@@ -139,7 +139,7 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetCommitFileStatusMergesSha256(t *testing.T) {
|
||||
bareRepo6Sha256 := &mockRepository{path: "repo6_merge_sha256"}
|
||||
bareRepo6Sha256 := mockRepository("repo6_merge_sha256")
|
||||
|
||||
commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6Sha256, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1")
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCommitsCount(t *testing.T) {
|
||||
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
||||
bareRepo1 := mockRepository("repo1_bare")
|
||||
|
||||
commitsCount, err := CommitsCount(t.Context(), bareRepo1,
|
||||
CommitsCountOptions{
|
||||
@@ -22,7 +22,7 @@ func TestCommitsCount(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCommitsCountWithSinceUntil(t *testing.T) {
|
||||
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
||||
bareRepo1 := mockRepository("repo1_bare")
|
||||
revision := []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"}
|
||||
|
||||
// The three commits on this revision are dated 2018-04-18, 2017-12-19 and 2017-12-19.
|
||||
@@ -53,7 +53,7 @@ func TestCommitsCountWithSinceUntil(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCommitsCountWithoutBase(t *testing.T) {
|
||||
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
||||
bareRepo1 := mockRepository("repo1_bare")
|
||||
|
||||
commitsCount, err := CommitsCount(t.Context(), bareRepo1,
|
||||
CommitsCountOptions{
|
||||
@@ -66,7 +66,7 @@ func TestCommitsCountWithoutBase(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetLatestCommitTime(t *testing.T) {
|
||||
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
||||
bareRepo1 := mockRepository("repo1_bare")
|
||||
lct, err := GetLatestCommitTime(t.Context(), bareRepo1)
|
||||
assert.NoError(t, err)
|
||||
// Time is Sun Nov 13 16:40:14 2022 +0100
|
||||
|
||||
@@ -15,14 +15,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockRepository struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (r *mockRepository) RelativePath() string {
|
||||
return r.path
|
||||
}
|
||||
|
||||
func TestMergeBaseNoCommonHistory(t *testing.T) {
|
||||
repoDir := filepath.Join(t.TempDir(), "repo.git")
|
||||
require.NoError(t, gitcmd.NewCommand("init").AddDynamicArguments(repoDir).Run(t.Context()))
|
||||
@@ -44,13 +36,13 @@ data 12
|
||||
Hello from 2
|
||||
`))).RunStdString(t.Context())
|
||||
require.NoError(t, runErr)
|
||||
mergeBase, err := MergeBase(t.Context(), &mockRepository{path: repoDir}, "branch1", "branch2")
|
||||
mergeBase, err := MergeBase(t.Context(), mockRepository(repoDir), "branch1", "branch2")
|
||||
assert.Empty(t, mergeBase)
|
||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||
}
|
||||
|
||||
func TestRepoGetDivergingCommits(t *testing.T) {
|
||||
repo := &mockRepository{path: "repo1_bare"}
|
||||
repo := mockRepository("repo1_bare")
|
||||
do, err := GetDivergingCommits(t.Context(), repo, "master", "branch2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &DivergeObject{
|
||||
@@ -74,7 +66,7 @@ func TestRepoGetDivergingCommits(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetCommitIDsBetweenReverse(t *testing.T) {
|
||||
repo := &mockRepository{path: "repo1_bare"}
|
||||
repo := mockRepository("repo1_bare")
|
||||
|
||||
// tests raw commit IDs
|
||||
commitIDs, err := GetCommitIDsBetweenReverse(t.Context(), repo,
|
||||
|
||||
@@ -6,17 +6,13 @@ package gitrepo
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/globallock"
|
||||
)
|
||||
|
||||
func getRepoConfigLockKey(repoStoragePath string) string {
|
||||
return "repo-config:" + repoStoragePath
|
||||
}
|
||||
|
||||
// GitConfigAdd add a git configuration key to a specific value for the given repository.
|
||||
func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error {
|
||||
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
|
||||
return git.LockConfigAndDo(ctx, repo, func(ctx context.Context) error {
|
||||
_, _, err := RunCmdString(ctx, repo, gitcmd.NewCommand("config", "--add").
|
||||
AddDynamicArguments(key, value))
|
||||
return err
|
||||
@@ -27,7 +23,7 @@ func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error
|
||||
// If the key does not exist, it will be created.
|
||||
// If the key exists, it will be updated to the new value.
|
||||
func GitConfigSet(ctx context.Context, repo Repository, key, value string) error {
|
||||
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
|
||||
return git.LockConfigAndDo(ctx, repo, func(ctx context.Context) error {
|
||||
_, _, err := RunCmdString(ctx, repo, gitcmd.NewCommand("config").
|
||||
AddDynamicArguments(key, value))
|
||||
return err
|
||||
|
||||
@@ -6,8 +6,8 @@ package gitrepo
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/globallock"
|
||||
)
|
||||
|
||||
// FetchRemoteCommit fetches a specific commit and its related objects from a remote
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// This behavior is sufficient for temporary operations, such as determining the
|
||||
// merge base between commits.
|
||||
func FetchRemoteCommit(ctx context.Context, repo, remoteRepo Repository, commitID string) error {
|
||||
return globallock.LockAndDo(ctx, getRepoWriteLockKey(repo.RelativePath()), func(ctx context.Context) error {
|
||||
return git.LockWriteAndDo(ctx, repo, func(ctx context.Context) error {
|
||||
return RunCmd(ctx, repo, gitcmd.NewCommand("fetch", "--no-tags").
|
||||
AddDynamicArguments(repoPath(remoteRepo)).
|
||||
AddDynamicArguments(commitID))
|
||||
|
||||
@@ -14,17 +14,12 @@ import (
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/reqctx"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
type Repository = git.RepositoryFacade
|
||||
type Repository = gitcmd.RepositoryFacade
|
||||
|
||||
// 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()))
|
||||
}
|
||||
var repoPath = gitcmd.RepoLocalPath
|
||||
|
||||
// OpenRepository opens the repository at the given relative path with the provided context.
|
||||
func OpenRepository(repo Repository) (*git.Repository, error) {
|
||||
@@ -33,7 +28,7 @@ func OpenRepository(repo Repository) (*git.Repository, error) {
|
||||
|
||||
// contextKey is a value for use with context.WithValue.
|
||||
type contextKey struct {
|
||||
repoPath string
|
||||
uniqueID string
|
||||
}
|
||||
|
||||
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
|
||||
@@ -51,11 +46,11 @@ func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Rep
|
||||
// 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)}
|
||||
ck := contextKey{uniqueID: repo.GitRepoUniqueID()}
|
||||
if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
|
||||
return gitRepo, nil
|
||||
}
|
||||
gitRepo, err := git.OpenRepository(ck.repoPath)
|
||||
gitRepo, err := git.OpenRepository(repoPath(repo))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -7,19 +7,20 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/repo"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// resolve repository path relative to the test directory
|
||||
setting.SetupGiteaTestEnv()
|
||||
giteaRoot := setting.GetGiteaTestSourceRoot()
|
||||
repoPath = func(repo Repository) string {
|
||||
if filepath.IsAbs(repo.RelativePath()) {
|
||||
return repo.RelativePath() // for testing purpose only
|
||||
}
|
||||
return filepath.Join(giteaRoot, "modules/git/tests/repos", repo.RelativePath())
|
||||
func mockRepository(repoPath string) repo.StorageRepo {
|
||||
if !filepath.IsAbs(repoPath) {
|
||||
// resolve repository path relative to the unit test fixture directory
|
||||
repoPath = filepath.Join(setting.GetGiteaTestSourceRoot(), "modules/git/tests/repos", repoPath)
|
||||
}
|
||||
return repo.StorageRepo(repoPath)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.SetupGiteaTestEnv()
|
||||
git.RunGitTests(m)
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ M 100644 :5 z/d
|
||||
func TestMergeTreeDirectoryRenameConflictWithoutFiles(t *testing.T) {
|
||||
repoDir := prepareRepoDirRenameConflict(t)
|
||||
require.DirExists(t, repoDir)
|
||||
repo := &mockRepository{path: repoDir}
|
||||
repo := mockRepository(repoDir)
|
||||
|
||||
mergeBase, err := MergeBase(t.Context(), repo, "add", "split")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
giturl "gitea.dev/modules/git/url"
|
||||
"gitea.dev/modules/globallock"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
@@ -22,7 +21,7 @@ const (
|
||||
)
|
||||
|
||||
func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL string, options ...RemoteOption) error {
|
||||
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
|
||||
return git.LockConfigAndDo(ctx, repo, func(ctx context.Context) error {
|
||||
cmd := gitcmd.NewCommand("remote", "add")
|
||||
if len(options) > 0 {
|
||||
switch options[0] {
|
||||
@@ -40,7 +39,7 @@ func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL st
|
||||
}
|
||||
|
||||
func GitRemoteRemove(ctx context.Context, repo Repository, remoteName string) error {
|
||||
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
|
||||
return git.LockConfigAndDo(ctx, repo, func(ctx context.Context) error {
|
||||
cmd := gitcmd.NewCommand("remote", "rm").AddDynamicArguments(remoteName)
|
||||
_, _, err := RunCmdString(ctx, repo, cmd)
|
||||
return err
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gitrepo
|
||||
|
||||
// getRepoWriteLockKey returns the global lock key for write operations on the repository.
|
||||
// Parallel write operations on the same git repository should be avoided to prevent data corruption.
|
||||
func getRepoWriteLockKey(repoStoragePath string) string {
|
||||
return "repo-write:" + repoStoragePath
|
||||
}
|
||||
Reference in New Issue
Block a user