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
+4 -7
View File
@@ -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))
}
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -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()
+4 -4
View File
@@ -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)
}
+2 -2
View File
@@ -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)
+4 -4
View File
@@ -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
+3 -11
View File
@@ -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,
+3 -7
View File
@@ -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
+2 -2
View File
@@ -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))
+5 -10
View File
@@ -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
}
+10 -9
View File
@@ -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)
}
+1 -1
View File
@@ -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)
+2 -3
View File
@@ -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
-10
View File
@@ -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
}