refactor: git repo and relative path handling (#38522)
1. simplify "StorageRepo" code 2. simplify git.OpenRepository code 3. by the way, clean up some unused files in git test fixtures.
This commit is contained in:
@@ -83,7 +83,7 @@ func AddCommitDivergenceToPulls(x db.EngineMigration) error {
|
|||||||
log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
|
log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
repoStore := repo_model.StorageRepo(repo_model.RelativePath(baseRepo.OwnerName, baseRepo.Name))
|
repoStore := repo_model.CodeRepoByName(baseRepo.OwnerName, baseRepo.Name)
|
||||||
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
|
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
|
||||||
divergence, err := gitrepo.GetDivergingCommits(graceful.GetManager().HammerContext(), repoStore, pr.BaseBranch, gitRefName)
|
divergence, err := gitrepo.GetDivergingCommits(graceful.GetManager().HammerContext(), repoStore, pr.BaseBranch, gitRefName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,24 +6,14 @@ package v1_14
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
|
repo_model "gitea.dev/models/repo"
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/setting"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Copy paste from models/repo.go because we cannot import models package
|
|
||||||
func repoPath(userName, repoName string) string {
|
|
||||||
return filepath.Join(userPath(userName), strings.ToLower(repoName)+".git")
|
|
||||||
}
|
|
||||||
|
|
||||||
func userPath(userName string) string {
|
|
||||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
|
|
||||||
}
|
|
||||||
|
|
||||||
func FixPublisherIDforTagReleases(ctx context.Context, x db.EngineMigration) error {
|
func FixPublisherIDforTagReleases(ctx context.Context, x db.EngineMigration) error {
|
||||||
type Release struct {
|
type Release struct {
|
||||||
ID int64
|
ID int64
|
||||||
@@ -108,7 +98,7 @@ func FixPublisherIDforTagReleases(ctx context.Context, x db.EngineMigration) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gitRepo, err = git.OpenRepository(repoPath(repo.OwnerName, repo.Name))
|
gitRepo, err = git.OpenRepository(repo_model.CodeRepoByName(repo.OwnerName, repo.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error whilst opening git repo for [%d]%s/%s. Error: %v", repo.ID, repo.OwnerName, repo.Name, err)
|
log.Error("Error whilst opening git repo for [%d]%s/%s. Error: %v", repo.ID, repo.OwnerName, repo.Name, err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -159,12 +159,12 @@ func migratePushMirrors(x db.EngineMigration) error {
|
|||||||
|
|
||||||
func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
|
func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
relativePath := repo_model.RelativePath(ownerName, repoName)
|
repo := repo_model.CodeRepoByName(ownerName, repoName)
|
||||||
if exist, _ := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(relativePath)); !exist {
|
if exist, _ := gitrepo.IsRepositoryExist(ctx, repo); !exist {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := gitrepo.GitRemoteGetURL(ctx, repo_model.StorageRepo(relativePath), remoteName)
|
u, err := gitrepo.GitRemoteGetURL(ctx, repo, remoteName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func FixReleaseSha1OnReleaseTable(ctx context.Context, x db.EngineMigration) err
|
|||||||
userCache[repo.OwnerID] = user
|
userCache[repo.OwnerID] = user
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err = gitrepo.OpenRepository(repo_model.StorageRepo(repo_model.RelativePath(user.Name, repo.Name)))
|
gitRepo, err = gitrepo.OpenRepository(repo_model.CodeRepoByName(user.Name, repo.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func (archiver *RepoArchiver) RelativePath() string {
|
|||||||
return fmt.Sprintf("%d/%s/%s.%s", archiver.RepoID, archiver.CommitID[:2], archiver.CommitID, archiver.Type.String())
|
return fmt.Sprintf("%d/%s/%s.%s", archiver.RepoID, archiver.CommitID[:2], archiver.CommitID, archiver.Type.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// repoArchiverForRelativePath takes a relativePath created from (archiver *RepoArchiver) RelativePath() and creates a shell repoArchiver struct representing it
|
// repoArchiverForRelativePath takes a relativePath created from RepoArchiver.RelativePath() and creates a shell repoArchiver struct representing it
|
||||||
func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) {
|
func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) {
|
||||||
parts := strings.SplitN(relativePath, "/", 3)
|
parts := strings.SplitN(relativePath, "/", 3)
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
|
|||||||
@@ -226,30 +226,6 @@ func init() {
|
|||||||
db.RegisterModel(new(Repository))
|
db.RegisterModel(new(Repository))
|
||||||
}
|
}
|
||||||
|
|
||||||
func RelativePath(ownerName, repoName string) string {
|
|
||||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (repo *Repository) GitRepoLocation() string {
|
|
||||||
return RelativePath(repo.OwnerName, repo.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (repo *Repository) GitRepoUniqueID() string {
|
|
||||||
return strconv.FormatInt(repo.ID, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
type StorageRepo string
|
|
||||||
|
|
||||||
func (sr StorageRepo) GitRepoUniqueID() string {
|
|
||||||
// TODO: need to correctly refactor this method in the future.
|
|
||||||
// "unique id" should be a cache-key-friendly string, but not a full repo path
|
|
||||||
return string(sr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sr StorageRepo) GitRepoLocation() string {
|
|
||||||
return string(sr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SanitizedOriginalURL returns a sanitized OriginalURL
|
// SanitizedOriginalURL returns a sanitized OriginalURL
|
||||||
func (repo *Repository) SanitizedOriginalURL() string {
|
func (repo *Repository) SanitizedOriginalURL() string {
|
||||||
if repo.OriginalURL == "" {
|
if repo.OriginalURL == "" {
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.dev/modules/git/gitcmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func repoCodeGitRepoRelativePath(ownerName, repoName string) string {
|
||||||
|
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git"
|
||||||
|
}
|
||||||
|
|
||||||
|
func repoWikiGitRepoRelativePath(ownerName, repoName string) string {
|
||||||
|
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeRepoByName returns an unmanaged repository facade for the code repository of the given owner and repository name.
|
||||||
|
// Usually it is used for migration fixes or repository adoption/creation/rename/transfer.
|
||||||
|
func CodeRepoByName(ownerName, repoName string) gitcmd.RepositoryFacade {
|
||||||
|
return gitcmd.RepositoryUnmanaged(repoCodeGitRepoRelativePath(ownerName, repoName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func WikiRepoByName(ownerName, repoName string) gitcmd.RepositoryFacade {
|
||||||
|
return gitcmd.RepositoryUnmanaged(repoWikiGitRepoRelativePath(ownerName, repoName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func repoCodeGitRepoManagedID(repoID int64) string {
|
||||||
|
return "repo-" + strconv.FormatInt(repoID, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Repository) CodeStorageRepo() gitcmd.RepositoryFacade {
|
||||||
|
id := repoCodeGitRepoManagedID(repo.ID)
|
||||||
|
repoPath := repoCodeGitRepoRelativePath(repo.OwnerName, repo.Name)
|
||||||
|
return gitcmd.RepositoryManaged(id, repoPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Repository) GitRepoLocation() string {
|
||||||
|
// TODO: use CodeGitRepo instead of this one
|
||||||
|
return repoCodeGitRepoRelativePath(repo.OwnerName, repo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Repository) GitRepoManagedID() string {
|
||||||
|
// TODO: use CodeGitRepo instead of this one
|
||||||
|
return repoCodeGitRepoManagedID(repo.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Repository) WikiStorageRepo() gitcmd.RepositoryFacade {
|
||||||
|
// The wiki repository should have the same object format as the code repository. TODO: old comment, REALLY? Why?
|
||||||
|
id := "repo-wiki-" + strconv.FormatInt(repo.ID, 10)
|
||||||
|
repoPath := repoWikiGitRepoRelativePath(repo.OwnerName, repo.Name)
|
||||||
|
return gitcmd.RepositoryManaged(id, repoPath)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
repo_model "gitea.dev/models/repo"
|
||||||
|
"gitea.dev/models/unittest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepository_GitRepo(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
|
||||||
|
assert.Equal(t, "user2/repo1.git", repo_model.CodeRepoByName(repo.OwnerName, repo.Name).GitRepoLocation())
|
||||||
|
assert.Equal(t, "user2/repo1.git", repo.CodeStorageRepo().GitRepoLocation())
|
||||||
|
assert.Equal(t, "repo-1", repo.CodeStorageRepo().GitRepoManagedID())
|
||||||
|
|
||||||
|
assert.Equal(t, "user2/repo1.wiki.git", repo_model.WikiRepoByName(repo.OwnerName, repo.Name).GitRepoLocation())
|
||||||
|
assert.Equal(t, "user2/repo1.wiki.git", repo.WikiStorageRepo().GitRepoLocation())
|
||||||
|
assert.Equal(t, "repo-wiki-1", repo.WikiStorageRepo().GitRepoManagedID())
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ package repo
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
@@ -74,13 +73,3 @@ func (err ErrWikiInvalidFileName) Unwrap() error {
|
|||||||
func (repo *Repository) WikiCloneLink(ctx context.Context, doer *user_model.User) *CloneLink {
|
func (repo *Repository) WikiCloneLink(ctx context.Context, doer *user_model.User) *CloneLink {
|
||||||
return repo.cloneLink(ctx, doer, repo.Name+".wiki")
|
return repo.cloneLink(ctx, doer, repo.Name+".wiki")
|
||||||
}
|
}
|
||||||
|
|
||||||
func RelativeWikiPath(ownerName, repoName string) string {
|
|
||||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
|
|
||||||
}
|
|
||||||
|
|
||||||
// WikiStorageRepo returns the storage repo for the wiki like "owner-name/repo-name.wiki.git"
|
|
||||||
// The wiki repository should have the same object format as the code repository. TODO: REALLY? Why?
|
|
||||||
func (repo *Repository) WikiStorageRepo() StorageRepo {
|
|
||||||
return StorageRepo(RelativeWikiPath(repo.OwnerName, repo.Name))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,11 +20,3 @@ func TestRepository_WikiCloneLink(t *testing.T) {
|
|||||||
assert.Equal(t, "ssh://sshuser@try.gitea.io:3000/user2/repo1.wiki.git", cloneLink.SSH)
|
assert.Equal(t, "ssh://sshuser@try.gitea.io:3000/user2/repo1.wiki.git", cloneLink.SSH)
|
||||||
assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS)
|
assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepository_RelativeWikiPath(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
||||||
assert.Equal(t, "user2/repo1.wiki.git", repo_model.RelativeWikiPath(repo.OwnerName, repo.Name))
|
|
||||||
assert.Equal(t, "user2/repo1.wiki.git", repo.WikiStorageRepo().GitRepoLocation())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
# Git Module
|
|
||||||
|
|
||||||
This module is merged from https://github.com/go-gitea/git which is a Go module to access Git through shell commands. Now it's a part of gitea's main repository for easier pull request.
|
|
||||||
@@ -119,7 +119,7 @@ func Test_BatchChecker(t *testing.T) {
|
|||||||
setting.AppDataPath = t.TempDir()
|
setting.AppDataPath = t.TempDir()
|
||||||
repoPath := "../tests/repos/language_stats_repo"
|
repoPath := "../tests/repos/language_stats_repo"
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
gitRepo, err := git.OpenRepository(repoPath)
|
gitRepo, err := git.OpenRepositoryLocal(repoPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ func Test_BatchChecker(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tempRepo, err := git.OpenRepository(dir)
|
tempRepo, err := git.OpenRepositoryLocal(dir)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer tempRepo.Close()
|
defer tempRepo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
func Test_Checker(t *testing.T) {
|
func Test_Checker(t *testing.T) {
|
||||||
setting.AppDataPath = t.TempDir()
|
setting.AppDataPath = t.TempDir()
|
||||||
repoPath := "../tests/repos/language_stats_repo"
|
repoPath := "../tests/repos/language_stats_repo"
|
||||||
gitRepo, err := git.OpenRepository(repoPath)
|
gitRepo, err := git.OpenRepositoryLocal(repoPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ func Test_Checker(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tempRepo, err := git.OpenRepository(dir)
|
tempRepo, err := git.OpenRepositoryLocal(dir)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer tempRepo.Close()
|
defer tempRepo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
func TestBlob_Data(t *testing.T) {
|
func TestBlob_Data(t *testing.T) {
|
||||||
output := "file2\n"
|
output := "file2\n"
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
repo, err := OpenRepository(bareRepo1Path)
|
repo, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ func TestBlob_Data(t *testing.T) {
|
|||||||
|
|
||||||
func Benchmark_Blob_Data(b *testing.B) {
|
func Benchmark_Blob_Data(b *testing.B) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
repo, err := OpenRepository(bareRepo1Path)
|
repo, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ package git
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"gitea.dev/modules/git/gitcmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BufferedReader interface {
|
type BufferedReader interface {
|
||||||
@@ -45,7 +47,8 @@ type CatFileBatchCloser interface {
|
|||||||
|
|
||||||
// NewBatch creates a "batch object provider (CatFileBatch)" for the given repository path to retrieve object info and content efficiently.
|
// NewBatch creates a "batch object provider (CatFileBatch)" for the given repository path to retrieve object info and content efficiently.
|
||||||
// The CatFileBatch and the readers create by it should only be used in the same goroutine.
|
// The CatFileBatch and the readers create by it should only be used in the same goroutine.
|
||||||
func NewBatch(ctx context.Context, repoPath string) (CatFileBatchCloser, error) {
|
func NewBatch(ctx context.Context, repo RepositoryFacade) (CatFileBatchCloser, error) {
|
||||||
|
repoPath := gitcmd.RepoLocalPath(repo)
|
||||||
if DefaultFeatures().SupportCatFileBatchCommand {
|
if DefaultFeatures().SupportCatFileBatchCommand {
|
||||||
return newCatFileBatchCommand(ctx, repoPath)
|
return newCatFileBatchCommand(ctx, repoPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gitea.dev/modules/git/gitcmd"
|
||||||
"gitea.dev/modules/test"
|
"gitea.dev/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -24,9 +25,10 @@ func TestCatFileBatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testCatFileBatch(t *testing.T) {
|
func testCatFileBatch(t *testing.T) {
|
||||||
|
repo1 := gitcmd.RepositoryUnmanaged(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
t.Run("CorruptedGitRepo", func(t *testing.T) {
|
t.Run("CorruptedGitRepo", func(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
batch, err := NewBatch(t.Context(), tmpDir)
|
batch, err := NewBatch(t.Context(), gitcmd.RepositoryUnmanaged(tmpDir))
|
||||||
// as long as the directory exists, no error, because we can't really know whether the git repo is valid until we run commands
|
// as long as the directory exists, no error, because we can't really know whether the git repo is valid until we run commands
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer batch.Close()
|
defer batch.Close()
|
||||||
@@ -47,7 +49,7 @@ func testCatFileBatch(t *testing.T) {
|
|||||||
assert.ErrorIs(t, err, expectedErr)
|
assert.ErrorIs(t, err, expectedErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
batch, err := NewBatch(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
|
batch, err := NewBatch(t.Context(), repo1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer batch.Close()
|
defer batch.Close()
|
||||||
_, err = batch.QueryInfo("e2129701f1a4d54dc44f03c93bca0a2aec7c5449")
|
_, err = batch.QueryInfo("e2129701f1a4d54dc44f03c93bca0a2aec7c5449")
|
||||||
@@ -79,7 +81,7 @@ func testCatFileBatch(t *testing.T) {
|
|||||||
simulateQueryTerminated(t, nil, os.ErrClosed) // pipes are closed faster
|
simulateQueryTerminated(t, nil, os.ErrClosed) // pipes are closed faster
|
||||||
})
|
})
|
||||||
|
|
||||||
batch, err := NewBatch(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
|
batch, err := NewBatch(t.Context(), repo1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer batch.Close()
|
defer batch.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) {
|
func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) {
|
||||||
repo, err := OpenRepository(filepath.Join(testReposDir, "repo1_bare"))
|
repo, err := OpenRepositoryLocal(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
testReposDir = "tests/repos/"
|
|
||||||
)
|
|
||||||
|
|
||||||
func cloneRepo(tb testing.TB, url string) (string, error) {
|
func cloneRepo(tb testing.TB, url string) (string, error) {
|
||||||
repoDir := tb.TempDir()
|
repoDir := tb.TempDir()
|
||||||
if err := Clone(tb.Context(), url, repoDir, CloneRepoOptions{
|
if err := Clone(tb.Context(), url, repoDir, CloneRepoOptions{
|
||||||
@@ -132,7 +128,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
|
|||||||
|
|
||||||
func TestEntries_GetCommitsInfo(t *testing.T) {
|
func TestEntries_GetCommitsInfo(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -142,7 +138,7 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
clonedRepo1, err := OpenRepository(clonedPath)
|
clonedRepo1, err := OpenRepositoryLocal(clonedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ signed commit`
|
|||||||
0x94, 0x33, 0xb2, 0xa6, 0x2b, 0x96, 0x4c, 0x17, 0xa4, 0x48, 0x5a, 0xe1, 0x80, 0xf4, 0x5f, 0x59,
|
0x94, 0x33, 0xb2, 0xa6, 0x2b, 0x96, 0x4c, 0x17, 0xa4, 0x48, 0x5a, 0xe1, 0x80, 0xf4, 0x5f, 0x59,
|
||||||
0x5d, 0x3e, 0x69, 0xd3, 0x1b, 0x78, 0x60, 0x87, 0x77, 0x5e, 0x28, 0xc6, 0xb6, 0x39, 0x9d, 0xf0,
|
0x5d, 0x3e, 0x69, 0xd3, 0x1b, 0x78, 0x60, 0x87, 0x77, 0x5e, 0x28, 0xc6, 0xb6, 0x39, 0x9d, 0xf0,
|
||||||
}
|
}
|
||||||
gitRepo, err := OpenRepository(filepath.Join(testReposDir, "repo1_bare_sha256"))
|
gitRepo, err := OpenRepositoryLocal(filepath.Join(testReposDir, "repo1_bare_sha256"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, gitRepo)
|
assert.NotNil(t, gitRepo)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
@@ -103,7 +103,7 @@ signed commit`, commitFromReader.Signature.Payload)
|
|||||||
func TestHasPreviousCommitSha256(t *testing.T) {
|
func TestHasPreviousCommitSha256(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
|
||||||
|
|
||||||
repo, err := OpenRepository(bareRepo1Path)
|
repo, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ gpgsig -----BEGIN PGP SIGNATURE-----
|
|||||||
empty commit`
|
empty commit`
|
||||||
|
|
||||||
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
||||||
gitRepo, err := OpenRepository(filepath.Join(testReposDir, "repo1_bare"))
|
gitRepo, err := OpenRepositoryLocal(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, gitRepo)
|
assert.NotNil(t, gitRepo)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
@@ -120,7 +120,7 @@ gpgsig -----BEGIN PGP SIGNATURE-----
|
|||||||
ISO-8859-1`
|
ISO-8859-1`
|
||||||
commitString = strings.ReplaceAll(commitString, "<SPACE>", " ")
|
commitString = strings.ReplaceAll(commitString, "<SPACE>", " ")
|
||||||
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
||||||
gitRepo, err := OpenRepository(filepath.Join(testReposDir, "repo1_bare"))
|
gitRepo, err := OpenRepositoryLocal(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, gitRepo)
|
assert.NotNil(t, gitRepo)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
@@ -162,7 +162,7 @@ ISO-8859-1`, commitFromReader.Signature.Payload)
|
|||||||
func TestHasPreviousCommit(t *testing.T) {
|
func TestHasPreviousCommit(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
|
|
||||||
repo, err := OpenRepository(bareRepo1Path)
|
repo, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ func TestHasPreviousCommit(t *testing.T) {
|
|||||||
|
|
||||||
func Test_GetCommitBranchStart(t *testing.T) {
|
func Test_GetCommitBranchStart(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
repo, err := OpenRepository(bareRepo1Path)
|
repo, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
commit, err := repo.GetBranchCommit(t.Context(), "branch1")
|
commit, err := repo.GetBranchCommit(t.Context(), "branch1")
|
||||||
|
|||||||
+2
-2
@@ -198,9 +198,9 @@ func runGitTests(m interface{ Run() int }) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LockConfigAndDo(ctx context.Context, repo RepositoryFacade, fn func(ctx context.Context) error) error {
|
func LockConfigAndDo(ctx context.Context, repo RepositoryFacade, fn func(ctx context.Context) error) error {
|
||||||
return globallock.LockAndDo(ctx, "repo-config:"+repo.GitRepoUniqueID(), fn)
|
return globallock.LockAndDo(ctx, "repo-config:"+repo.GitRepoManagedID(), fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LockWriteAndDo(ctx context.Context, repo RepositoryFacade, fn func(ctx context.Context) error) error {
|
func LockWriteAndDo(ctx context.Context, repo RepositoryFacade, fn func(ctx context.Context) error) error {
|
||||||
return globallock.LockAndDo(ctx, "repo-write:"+repo.GitRepoUniqueID(), fn)
|
return globallock.LockAndDo(ctx, "repo-write:"+repo.GitRepoManagedID(), fn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const testReposDir = "tests/repos/"
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
RunGitTests(m)
|
RunGitTests(m)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RepositoryFacade interface {
|
type RepositoryFacade interface {
|
||||||
GitRepoUniqueID() string
|
// GitRepoManagedID returns a "managed id", which should be a cache-key-friendly string.
|
||||||
|
// e.g.: ID with prefix&suffix or UUID
|
||||||
|
GitRepoManagedID() string
|
||||||
|
|
||||||
|
// GitRepoLocation returns the location for the git repository.
|
||||||
|
// * relative path: will be converted to an absolute path by setting.RepoRootPath
|
||||||
|
// * absolute path: will be used as-is
|
||||||
|
// * in the future: maybe URI for more flexible definitions
|
||||||
GitRepoLocation() string
|
GitRepoLocation() string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,3 +33,34 @@ func RepoLocalPath(repo RepositoryFacade) string {
|
|||||||
}
|
}
|
||||||
return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repoLoc))
|
return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repoLoc))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type repositoryUnmanaged string
|
||||||
|
|
||||||
|
func (r repositoryUnmanaged) GitRepoManagedID() string {
|
||||||
|
panic("this repo is not managed by Gitea, can't be used in this managed context")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r repositoryUnmanaged) GitRepoLocation() string {
|
||||||
|
return string(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RepositoryUnmanaged(s string) RepositoryFacade {
|
||||||
|
return repositoryUnmanaged(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
type repositoryManaged struct {
|
||||||
|
id string
|
||||||
|
loc string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositoryManaged) GitRepoManagedID() string {
|
||||||
|
return r.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositoryManaged) GitRepoLocation() string {
|
||||||
|
return r.loc
|
||||||
|
}
|
||||||
|
|
||||||
|
func RepositoryManaged(id, loc string) RepositoryFacade {
|
||||||
|
return &repositoryManaged{id, loc}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGrepSearch(t *testing.T) {
|
func TestGrepSearch(t *testing.T) {
|
||||||
repo, err := OpenRepository(filepath.Join(testReposDir, "language_stats_repo"))
|
repo, err := OpenRepositoryLocal(filepath.Join(testReposDir, "language_stats_repo"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
func TestRepository_GetLanguageStats(t *testing.T) {
|
func TestRepository_GetLanguageStats(t *testing.T) {
|
||||||
setting.AppDataPath = t.TempDir()
|
setting.AppDataPath = t.TempDir()
|
||||||
repoPath := "../tests/repos/language_stats_repo"
|
repoPath := "../tests/repos/language_stats_repo"
|
||||||
gitRepo, err := git.OpenRepository(repoPath)
|
gitRepo, err := git.OpenRepositoryLocal(repoPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func TestGetNotes(t *testing.T) {
|
func TestGetNotes(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ func TestGetNotes(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetNestedNotes(t *testing.T) {
|
func TestGetNestedNotes(t *testing.T) {
|
||||||
repoPath := filepath.Join(testReposDir, "repo3_notes")
|
repoPath := filepath.Join(testReposDir, "repo3_notes")
|
||||||
repo, err := OpenRepository(repoPath)
|
repo, err := OpenRepositoryLocal(repoPath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ func TestGetNestedNotes(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetNonExistentNotes(t *testing.T) {
|
func TestGetNonExistentNotes(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
func TestFindLFSFile(t *testing.T) {
|
func TestFindLFSFile(t *testing.T) {
|
||||||
repoPath := "../../../tests/gitea-repositories-meta/user2/lfs.git"
|
repoPath := "../../../tests/gitea-repositories-meta/user2/lfs.git"
|
||||||
gitRepo, err := git.OpenRepository(repoPath)
|
gitRepo, err := git.OpenRepositoryLocal(repoPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
|||||||
+24
-6
@@ -28,15 +28,23 @@ type RepositoryBase struct {
|
|||||||
|
|
||||||
LastCommitCache *LastCommitCache
|
LastCommitCache *LastCommitCache
|
||||||
|
|
||||||
|
repoFacade RepositoryFacade
|
||||||
tagCache *ObjectCache[*Tag]
|
tagCache *ObjectCache[*Tag]
|
||||||
objectFormatCache ObjectFormat
|
objectFormatCache ObjectFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenRepository(repoPath string) (*Repository, error) {
|
var _ gitcmd.RepositoryFacade = (*Repository)(nil)
|
||||||
repoPath, err := filepath.Abs(repoPath)
|
|
||||||
if err != nil {
|
func (repo *Repository) GitRepoManagedID() string {
|
||||||
return nil, err
|
return repo.repoFacade.GitRepoManagedID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *Repository) GitRepoLocation() string {
|
||||||
|
return repo.repoFacade.GitRepoLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenRepository(repo RepositoryFacade) (*Repository, error) {
|
||||||
|
repoPath := gitcmd.RepoLocalPath(repo)
|
||||||
exist, err := util.IsDir(repoPath)
|
exist, err := util.IsDir(repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -45,7 +53,7 @@ func OpenRepository(repoPath string) (*Repository, error) {
|
|||||||
return nil, util.NewNotExistErrorf("no such file or directory")
|
return nil, util.NewNotExistErrorf("no such file or directory")
|
||||||
}
|
}
|
||||||
gitRepo := &Repository{
|
gitRepo := &Repository{
|
||||||
RepositoryBase: RepositoryBase{Path: repoPath, tagCache: newObjectCache[*Tag]()},
|
RepositoryBase: RepositoryBase{Path: repoPath, tagCache: newObjectCache[*Tag](), repoFacade: repo},
|
||||||
}
|
}
|
||||||
if err = openRepositoryInternal(gitRepo); err != nil {
|
if err = openRepositoryInternal(gitRepo); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -53,6 +61,16 @@ func OpenRepository(repoPath string) (*Repository, error) {
|
|||||||
return gitRepo, nil
|
return gitRepo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func OpenRepositoryLocal(localPath string) (_ *Repository, err error) {
|
||||||
|
if !filepath.IsAbs(localPath) {
|
||||||
|
localPath, err = filepath.Abs(localPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OpenRepository(gitcmd.RepositoryUnmanaged(localPath))
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *Repository) Close() error {
|
func (repo *Repository) Close() error {
|
||||||
if repo == nil {
|
if repo == nil {
|
||||||
setting.PanicInDevOrTesting("don't close a nil repository")
|
setting.PanicInDevOrTesting("don't close a nil repository")
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (repo *Repository) CatFileBatch(ctx context.Context) (_ CatFileBatch, close
|
|||||||
}
|
}
|
||||||
|
|
||||||
if repo.catFileBatchCloser == nil {
|
if repo.catFileBatchCloser == nil {
|
||||||
repo.catFileBatchCloser, err = NewBatch(ctx, repo.Path)
|
repo.catFileBatchCloser, err = NewBatch(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
repo.catFileBatchCloser = nil // otherwise it is "interface(nil)" and will cause wrong logic
|
repo.catFileBatchCloser = nil // otherwise it is "interface(nil)" and will cause wrong logic
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -59,7 +59,7 @@ func (repo *Repository) CatFileBatch(ctx context.Context) (_ CatFileBatch, close
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Opening temporary cat file batch for: %s", repo.Path)
|
log.Debug("Opening temporary cat file batch for: %s", repo.Path)
|
||||||
tempBatch, err := NewBatch(ctx, repo.Path)
|
tempBatch, err := NewBatch(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -14,9 +15,13 @@ import (
|
|||||||
|
|
||||||
func TestRepoCatFileBatch(t *testing.T) {
|
func TestRepoCatFileBatch(t *testing.T) {
|
||||||
t.Run("MissingRepoAndClose", func(t *testing.T) {
|
t.Run("MissingRepoAndClose", func(t *testing.T) {
|
||||||
repo, err := OpenRepository(filepath.Join(testReposDir, "repo1_bare"))
|
testDir := filepath.Join(t.TempDir(), "testdir")
|
||||||
|
_ = os.Mkdir(testDir, 0o755)
|
||||||
|
repo, err := OpenRepositoryLocal(testDir)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// when the repo is missing (it usually occurs during testing because the fixtures are synced frequently)
|
||||||
|
err = os.Remove(testDir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo.Path = "/no-such" // when the repo is missing (it usually occurs during testing because the fixtures are synced frequently)
|
|
||||||
_, _, err = repo.CatFileBatch(t.Context())
|
_, _, err = repo.CatFileBatch(t.Context())
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.NoError(t, repo.Close()) // shouldn't panic
|
require.NoError(t, repo.Close()) // shouldn't panic
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
func TestRepository_GetBlob_Found(t *testing.T) {
|
func TestRepository_GetBlob_Found(t *testing.T) {
|
||||||
repoPath := filepath.Join(testReposDir, "repo1_bare")
|
repoPath := filepath.Join(testReposDir, "repo1_bare")
|
||||||
r, err := OpenRepository(repoPath)
|
r, err := OpenRepositoryLocal(repoPath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ func TestRepository_GetBlob_Found(t *testing.T) {
|
|||||||
|
|
||||||
func TestRepository_GetBlob_NotExist(t *testing.T) {
|
func TestRepository_GetBlob_NotExist(t *testing.T) {
|
||||||
repoPath := filepath.Join(testReposDir, "repo1_bare")
|
repoPath := filepath.Join(testReposDir, "repo1_bare")
|
||||||
r, err := OpenRepository(repoPath)
|
r, err := OpenRepositoryLocal(repoPath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ func TestRepository_GetBlob_NotExist(t *testing.T) {
|
|||||||
|
|
||||||
func TestRepository_GetBlob_NoId(t *testing.T) {
|
func TestRepository_GetBlob_NoId(t *testing.T) {
|
||||||
repoPath := filepath.Join(testReposDir, "repo1_bare")
|
repoPath := filepath.Join(testReposDir, "repo1_bare")
|
||||||
r, err := OpenRepository(repoPath)
|
r, err := OpenRepositoryLocal(repoPath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func TestRepository_GetBranches(t *testing.T) {
|
func TestRepository_GetBranches(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ func TestRepository_GetBranches(t *testing.T) {
|
|||||||
|
|
||||||
func BenchmarkRepository_GetBranches(b *testing.B) {
|
func BenchmarkRepository_GetBranches(b *testing.B) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ func BenchmarkRepository_GetBranches(b *testing.B) {
|
|||||||
|
|
||||||
func TestGetRefsBySha(t *testing.T) {
|
func TestGetRefsBySha(t *testing.T) {
|
||||||
bareRepo5Path := filepath.Join(testReposDir, "repo5_pulls")
|
bareRepo5Path := filepath.Join(testReposDir, "repo5_pulls")
|
||||||
bareRepo5, err := OpenRepository(bareRepo5Path)
|
bareRepo5, err := OpenRepositoryLocal(bareRepo5Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ func TestGetRefsBySha(t *testing.T) {
|
|||||||
|
|
||||||
func BenchmarkGetRefsBySha(b *testing.B) {
|
func BenchmarkGetRefsBySha(b *testing.B) {
|
||||||
bareRepo5Path := filepath.Join(testReposDir, "repo5_pulls")
|
bareRepo5Path := filepath.Join(testReposDir, "repo5_pulls")
|
||||||
bareRepo5, err := OpenRepository(bareRepo5Path)
|
bareRepo5, err := OpenRepositoryLocal(bareRepo5Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ func BenchmarkGetRefsBySha(b *testing.B) {
|
|||||||
|
|
||||||
func TestRepository_IsObjectExist(t *testing.T) {
|
func TestRepository_IsObjectExist(t *testing.T) {
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
repo, err := OpenRepository(filepath.Join(testReposDir, "repo1_bare"))
|
repo, err := OpenRepositoryLocal(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ func TestRepository_IsObjectExist(t *testing.T) {
|
|||||||
|
|
||||||
func TestRepository_IsReferenceExist(t *testing.T) {
|
func TestRepository_IsReferenceExist(t *testing.T) {
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
repo, err := OpenRepository(filepath.Join(testReposDir, "repo1_bare"))
|
repo, err := OpenRepositoryLocal(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
|
|
||||||
func TestRepository_GetCommitBranches(t *testing.T) {
|
func TestRepository_GetCommitBranches(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ func TestRepository_GetCommitBranches(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetTagCommitWithSignature(t *testing.T) {
|
func TestGetTagCommitWithSignature(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ func TestGetTagCommitWithSignature(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetCommitWithBadCommitID(t *testing.T) {
|
func TestGetCommitWithBadCommitID(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ func TestGetCommitWithBadCommitID(t *testing.T) {
|
|||||||
|
|
||||||
func TestIsCommitInBranch(t *testing.T) {
|
func TestIsCommitInBranch(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ func TestIsCommitInBranch(t *testing.T) {
|
|||||||
|
|
||||||
func TestRepository_CommitsBetween(t *testing.T) {
|
func TestRepository_CommitsBetween(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo4_commitsbetween")
|
bareRepo1Path := filepath.Join(testReposDir, "repo4_commitsbetween")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ func TestRepository_CommitsBetween(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetRefCommitID(t *testing.T) {
|
func TestGetRefCommitID(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ func TestCommitsByFileAndRange(t *testing.T) {
|
|||||||
defer test.MockVariableValue(&setting.Git.CommitsRangeSize, 2)()
|
defer test.MockVariableValue(&setting.Git.CommitsRangeSize, 2)()
|
||||||
|
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ M 100644 :1 b.txt
|
|||||||
`))).RunStdString(t.Context())
|
`))).RunStdString(t.Context())
|
||||||
require.NoError(t, runErr)
|
require.NoError(t, runErr)
|
||||||
|
|
||||||
repoFollowRename, err := OpenRepository(repoFollowRenameDir)
|
repoFollowRename, err := OpenRepositoryLocal(repoFollowRenameDir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer repoFollowRename.Close()
|
defer repoFollowRename.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import (
|
|||||||
|
|
||||||
// WriteCommitGraph write commit graph to speed up repo access
|
// WriteCommitGraph write commit graph to speed up repo access
|
||||||
// this requires git v2.18 to be installed
|
// this requires git v2.18 to be installed
|
||||||
func WriteCommitGraph(ctx context.Context, repoPath string) error {
|
func WriteCommitGraph(ctx context.Context, repo RepositoryFacade) error {
|
||||||
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||||
if _, _, err := gitcmd.NewCommand("commit-graph", "write").WithDir(repoPath).RunStdString(ctx); err != nil {
|
if _, _, err := gitcmd.NewCommand("commit-graph", "write").WithRepo(repo).RunStdString(ctx); err != nil {
|
||||||
return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err)
|
return fmt.Errorf("unable to write commit-graph for '%s' : %w", repo.GitRepoLocation(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func TestGetFormatPatch(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := OpenRepository(clonedPath)
|
repo, err := OpenRepositoryLocal(clonedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return
|
return
|
||||||
@@ -50,7 +50,7 @@ func TestGetFormatPatch(t *testing.T) {
|
|||||||
func TestReadPatch(t *testing.T) {
|
func TestReadPatch(t *testing.T) {
|
||||||
// Ensure we can read the patch files
|
// Ensure we can read the patch files
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
repo, err := OpenRepository(bareRepo1Path)
|
repo, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return
|
return
|
||||||
@@ -88,7 +88,7 @@ func TestReadWritePullHead(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := OpenRepository(clonedPath)
|
repo, err := OpenRepositoryLocal(clonedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return
|
return
|
||||||
@@ -130,7 +130,7 @@ func TestReadWritePullHead(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetCommitFilesChanged(t *testing.T) {
|
func TestGetCommitFilesChanged(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
repo, err := OpenRepository(bareRepo1Path)
|
repo, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func TestRepository_GetRefs(t *testing.T) {
|
func TestRepository_GetRefs(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ func TestRepository_GetRefs(t *testing.T) {
|
|||||||
|
|
||||||
func TestRepository_GetRefsFiltered(t *testing.T) {
|
func TestRepository_GetRefsFiltered(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func TestRepository_GetCodeActivityStats(t *testing.T) {
|
func TestRepository_GetCodeActivityStats(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func TestRepository_GetTagInfos(t *testing.T) {
|
func TestRepository_GetTagInfos(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
bareRepo1, err := OpenRepositoryLocal(bareRepo1Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return
|
return
|
||||||
@@ -44,7 +44,7 @@ func TestRepository_GetTag(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bareRepo1, err := OpenRepository(clonedPath)
|
bareRepo1, err := OpenRepositoryLocal(clonedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return
|
return
|
||||||
@@ -136,7 +136,7 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bareRepo1, err := OpenRepository(clonedPath)
|
bareRepo1, err := OpenRepositoryLocal(clonedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
func TestRepoIsEmpty(t *testing.T) {
|
func TestRepoIsEmpty(t *testing.T) {
|
||||||
emptyRepo2Path := filepath.Join(testReposDir, "repo2_empty")
|
emptyRepo2Path := filepath.Join(testReposDir, "repo2_empty")
|
||||||
repo, err := OpenRepository(emptyRepo2Path)
|
repo, err := OpenRepositoryLocal(emptyRepo2Path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
isEmpty, err := repo.IsEmpty(t.Context())
|
isEmpty, err := repo.IsEmpty(t.Context())
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
Add some test files for GetLanguageStats
|
|
||||||
|
|
||||||
Signed-off-by: Andrew Thornton <art27@cantab.net>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
2
|
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFollowLink(t *testing.T) {
|
func TestFollowLink(t *testing.T) {
|
||||||
r, err := OpenRepository("tests/repos/repo1_bare")
|
r, err := OpenRepositoryLocal(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestSubTree_Issue29101(t *testing.T) {
|
func TestSubTree_Issue29101(t *testing.T) {
|
||||||
repo, err := OpenRepository(filepath.Join(testReposDir, "repo1_bare"))
|
repo, err := OpenRepositoryLocal(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ func TestSubTree_Issue29101(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_GetTreePathLatestCommit(t *testing.T) {
|
func Test_GetTreePathLatestCommit(t *testing.T) {
|
||||||
repo, err := OpenRepository(filepath.Join(testReposDir, "repo6_blame"))
|
repo, err := OpenRepositoryLocal(filepath.Join(testReposDir, "repo6_blame"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package gitrepo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.dev/modules/git"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewBatch(ctx context.Context, repo Repository) (git.CatFileBatchCloser, error) {
|
|
||||||
return git.NewBatch(ctx, repoPath(repo))
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"gitea.dev/modules/git/gitcmd"
|
"gitea.dev/modules/git/gitcmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: all wrappers can be removed in next PR, because cmd now can accept Repository directly.
|
||||||
|
|
||||||
func RunCmd(ctx context.Context, repo Repository, cmd *gitcmd.Command) error {
|
func RunCmd(ctx context.Context, repo Repository, cmd *gitcmd.Command) error {
|
||||||
return cmd.WithRepo(repo).WithParentCallerInfo().Run(ctx)
|
return cmd.WithRepo(repo).WithParentCallerInfo().Run(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package gitrepo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.dev/modules/git"
|
|
||||||
)
|
|
||||||
|
|
||||||
func WriteCommitGraph(ctx context.Context, repo Repository) error {
|
|
||||||
return git.WriteCommitGraph(ctx, repoPath(repo))
|
|
||||||
}
|
|
||||||
@@ -19,16 +19,14 @@ import (
|
|||||||
|
|
||||||
type Repository = gitcmd.RepositoryFacade
|
type Repository = gitcmd.RepositoryFacade
|
||||||
|
|
||||||
var repoPath = gitcmd.RepoLocalPath
|
var (
|
||||||
|
repoPath = gitcmd.RepoLocalPath
|
||||||
// OpenRepository opens the repository at the given relative path with the provided context.
|
OpenRepository = git.OpenRepository
|
||||||
func OpenRepository(repo Repository) (*git.Repository, error) {
|
)
|
||||||
return git.OpenRepository(repoPath(repo))
|
|
||||||
}
|
|
||||||
|
|
||||||
// contextKey is a value for use with context.WithValue.
|
// contextKey is a value for use with context.WithValue.
|
||||||
type contextKey struct {
|
type contextKey struct {
|
||||||
uniqueID string
|
key string
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
|
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
|
||||||
@@ -46,11 +44,11 @@ func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Rep
|
|||||||
// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context.
|
// 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.
|
// 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) {
|
func RepositoryFromRequestContextOrOpen(ctx reqctx.RequestContext, repo Repository) (*git.Repository, error) {
|
||||||
ck := contextKey{uniqueID: repo.GitRepoUniqueID()}
|
ck := contextKey{key: repo.GitRepoLocation()}
|
||||||
if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
|
if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
|
||||||
return gitRepo, nil
|
return gitRepo, nil
|
||||||
}
|
}
|
||||||
gitRepo, err := git.OpenRepository(repoPath(repo))
|
gitRepo, err := git.OpenRepository(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.dev/models/repo"
|
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
|
"gitea.dev/modules/git/gitcmd"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mockRepository(repoPath string) repo.StorageRepo {
|
func mockRepository(repoPath string) gitcmd.RepositoryFacade {
|
||||||
if !filepath.IsAbs(repoPath) {
|
if !filepath.IsAbs(repoPath) {
|
||||||
// resolve repository path relative to the unit test fixture directory
|
// resolve repository path relative to the unit test fixture directory
|
||||||
repoPath = filepath.Join(setting.GetGiteaTestSourceRoot(), "modules/git/tests/repos", repoPath)
|
repoPath = filepath.Join(setting.GetGiteaTestSourceRoot(), "modules/git/tests/repos", repoPath)
|
||||||
}
|
}
|
||||||
return repo.StorageRepo(repoPath)
|
return gitcmd.RepositoryManaged(repoPath, repoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository, batch
|
|||||||
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
|
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
|
||||||
batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize)
|
batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize)
|
||||||
if len(changes.Updates) > 0 {
|
if len(changes.Updates) > 0 {
|
||||||
catfileBatch, err := gitrepo.NewBatch(ctx, repo)
|
catfileBatch, err := git.NewBatch(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository) es.Bul
|
|||||||
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
|
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
|
||||||
ops := make([]es.BulkOp, 0)
|
ops := make([]es.BulkOp, 0)
|
||||||
if len(changes.Updates) > 0 {
|
if len(changes.Updates) > 0 {
|
||||||
batch, err := gitrepo.NewBatch(ctx, repo)
|
batch, err := git.NewBatch(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func AdoptRepository(ctx *context.APIContext) {
|
|||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(repo_model.RelativePath(ctxUser.Name, repoName)))
|
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.CodeRepoByName(ctxUser.Name, repoName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -161,7 +161,7 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) {
|
|||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(repo_model.RelativePath(ctxUser.Name, repoName)))
|
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.CodeRepoByName(ctxUser.Name, repoName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -10,18 +10,15 @@ import (
|
|||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testReposDir = "tests/repos/"
|
|
||||||
|
|
||||||
func TestVerifyCommits(t *testing.T) {
|
func TestVerifyCommits(t *testing.T) {
|
||||||
unittest.PrepareTestEnv(t)
|
unittest.PrepareTestEnv(t)
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(testReposDir + "repo1_hook_verification")
|
gitRepo, err := git.OpenRepositoryLocal("tests/repos/repo1_hook_verification")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
}
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
objectFormat, err := gitRepo.GetObjectFormat(t.Context())
|
objectFormat, err := gitRepo.GetObjectFormat(t.Context())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"gitea.dev/models/unit"
|
"gitea.dev/models/unit"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
|
"gitea.dev/modules/git/gitcmd"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/private"
|
"gitea.dev/modules/private"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
@@ -318,7 +319,8 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results.RepoStoragePath = util.Iif(results.IsWiki, repo_model.RelativeWikiPath(repo.OwnerName, repo.Name), repo.GitRepoLocation())
|
gitRepo := util.Iif(results.IsWiki, repo.WikiStorageRepo(), repo.CodeStorageRepo())
|
||||||
|
results.RepoStoragePath = gitcmd.RepoLocalPath(gitRepo)
|
||||||
log.Debug("Serv Results: %+v", results)
|
log.Debug("Serv Results: %+v", results)
|
||||||
ctx.JSON(http.StatusOK, results)
|
ctx.JSON(http.StatusOK, results)
|
||||||
// We will update the keys in a different call.
|
// We will update the keys in a different call.
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
|
|||||||
ctx.ServerError("IsRepositoryExist", err)
|
ctx.ServerError("IsRepositoryExist", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(repo_model.RelativePath(ctxUser.Name, repoName)))
|
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.CodeRepoByName(ctxUser.Name, repoName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("IsDir", err)
|
ctx.ServerError("IsDir", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ func LFSLocks(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(tmpBasePath)
|
gitRepo, err := git.OpenRepositoryLocal(tmpBasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to open temporary repository: %s (%v)", tmpBasePath, err)
|
log.Error("Unable to open temporary repository: %s (%v)", tmpBasePath, err)
|
||||||
ctx.ServerError("LFSLocks", fmt.Errorf("failed to open new temporary repository in: %s %w", tmpBasePath, err))
|
ctx.ServerError("LFSLocks", fmt.Errorf("failed to open new temporary repository in: %s %w", tmpBasePath, err))
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ data 12
|
|||||||
err = gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(stdin)).RunWithStderr(t.Context())
|
err = gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(stdin)).RunWithStderr(t.Context())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(repoPath)
|
gitRepo, err := git.OpenRepositoryLocal(repoPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(repo_model.RelativePath(ctxUser.Name, dir)))
|
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.CodeRepoByName(ctxUser.Name, dir))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("IsDir", err)
|
ctx.ServerError("IsDir", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func TestGetActionWorkflow_FallbackRef(t *testing.T) {
|
|||||||
|
|
||||||
repoDir := buildWorkflowTestRepo(t)
|
repoDir := buildWorkflowTestRepo(t)
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(repoDir)
|
gitRepo, err := git.OpenRepositoryLocal(repoDir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
|
"gitea.dev/modules/git"
|
||||||
"gitea.dev/modules/gitrepo"
|
"gitea.dev/modules/gitrepo"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/structs"
|
"gitea.dev/modules/structs"
|
||||||
@@ -162,7 +163,7 @@ func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) erro
|
|||||||
if !isExist {
|
if !isExist {
|
||||||
numNeedUpdate++
|
numNeedUpdate++
|
||||||
if autofix {
|
if autofix {
|
||||||
if err := gitrepo.WriteCommitGraph(ctx, repo); err != nil {
|
if err := git.WriteCommitGraph(ctx, repo); err != nil {
|
||||||
logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err)
|
logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ func TestGitDiffTree(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range test {
|
for _, tt := range test {
|
||||||
t.Run(tt.Name, func(t *testing.T) {
|
t.Run(tt.Name, func(t *testing.T) {
|
||||||
gitRepo, err := git.OpenRepository(tt.RepoPath)
|
gitRepo, err := git.OpenRepositoryLocal(tt.RepoPath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ func TestGitDiffTreeErrors(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range test {
|
for _, tt := range test {
|
||||||
t.Run(tt.Name, func(t *testing.T) {
|
t.Run(tt.Name, func(t *testing.T) {
|
||||||
gitRepo, err := git.OpenRepository(tt.RepoPath)
|
gitRepo, err := git.OpenRepositoryLocal(tt.RepoPath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -601,7 +601,7 @@ func TestDiffLine_GetCommentSide(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) {
|
func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) {
|
||||||
gitRepo, err := git.OpenRepository("../../modules/git/tests/repos/repo5_pulls")
|
gitRepo, err := git.OpenRepositoryLocal("../../modules/git/tests/repos/repo5_pulls")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
@@ -1188,7 +1188,7 @@ D test2.txt
|
|||||||
D test10.txt`
|
D test10.txt`
|
||||||
require.NoError(t, gitcmd.NewCommand("fast-import").WithDir(pull.BaseRepo.RepoPath()).WithStdinBytes([]byte(stdin)).Run(t.Context()))
|
require.NoError(t, gitcmd.NewCommand("fast-import").WithDir(pull.BaseRepo.RepoPath()).WithStdinBytes([]byte(stdin)).Run(t.Context()))
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(pull.BaseRepo.RepoPath())
|
gitRepo, err := git.OpenRepositoryLocal(pull.BaseRepo.RepoPath())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -157,7 +157,9 @@ func (g *RepositoryDumper) CreateRepo(ctx context.Context, repo *base.Repository
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Clone: %w", err)
|
return fmt.Errorf("Clone: %w", err)
|
||||||
}
|
}
|
||||||
if err := git.WriteCommitGraph(ctx, repoPath); err != nil {
|
|
||||||
|
repoLocal := gitcmd.RepositoryUnmanaged(repoPath)
|
||||||
|
if err := git.WriteCommitGraph(ctx, repoLocal); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +170,7 @@ func (g *RepositoryDumper) CreateRepo(ctx context.Context, repo *base.Repository
|
|||||||
if err := os.MkdirAll(wikiPath, os.ModePerm); err != nil {
|
if err := os.MkdirAll(wikiPath, os.ModePerm); err != nil {
|
||||||
return fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
|
return fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
|
||||||
}
|
}
|
||||||
|
wikiLocal := gitcmd.RepositoryUnmanaged(wikiPath)
|
||||||
if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{
|
if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{
|
||||||
Mirror: true,
|
Mirror: true,
|
||||||
Quiet: true,
|
Quiet: true,
|
||||||
@@ -180,13 +182,13 @@ func (g *RepositoryDumper) CreateRepo(ctx context.Context, repo *base.Repository
|
|||||||
if err := os.RemoveAll(wikiPath); err != nil {
|
if err := os.RemoveAll(wikiPath); err != nil {
|
||||||
return fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
|
return fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
|
||||||
}
|
}
|
||||||
} else if err := git.WriteCommitGraph(ctx, wikiPath); err != nil {
|
} else if err := git.WriteCommitGraph(ctx, wikiLocal); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.gitRepo, err = git.OpenRepository(g.gitPath())
|
g.gitRepo, err = git.OpenRepositoryLocal(g.gitPath())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResu
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := gitrepo.WriteCommitGraph(ctx, m.Repo); err != nil {
|
if err := git.WriteCommitGraph(ctx, m.Repo); err != nil {
|
||||||
log.Error("SyncMirrors [repo: %-v]: %v", m.Repo, err)
|
log.Error("SyncMirrors [repo: %-v]: %v", m.Repo, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResu
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitrepo.WriteCommitGraph(ctx, m.Repo.WikiStorageRepo()); err != nil {
|
if err := git.WriteCommitGraph(ctx, m.Repo.WikiStorageRepo()); err != nil {
|
||||||
log.Error("SyncMirrors [repo: %-v]: %v", m.Repo, err)
|
log.Error("SyncMirrors [repo: %-v]: %v", m.Repo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque
|
|||||||
mergeCtx.sig = doer.NewGitSig()
|
mergeCtx.sig = doer.NewGitSig()
|
||||||
mergeCtx.committer = mergeCtx.sig
|
mergeCtx.committer = mergeCtx.sig
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(mergeCtx.tmpBasePath)
|
gitRepo, err := git.OpenRepositoryLocal(mergeCtx.tmpBasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return nil, nil, fmt.Errorf("failed to open temp git repo for pr[%d]: %w", mergeCtx.pr.ID, err)
|
return nil, nil, fmt.Errorf("failed to open temp git repo for pr[%d]: %w", mergeCtx.pr.ID, err)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func getAuthorSignatureSquash(ctx *mergeContext) (*git.Signature, error) {
|
|||||||
// Try to get a signature from the same user in one of the commits, as the
|
// Try to get a signature from the same user in one of the commits, as the
|
||||||
// poster email might be private or commits might have a different signature
|
// poster email might be private or commits might have a different signature
|
||||||
// than the primary email address of the poster.
|
// than the primary email address of the poster.
|
||||||
gitRepo, err := git.OpenRepository(ctx.tmpBasePath)
|
gitRepo, err := git.OpenRepositoryLocal(ctx.tmpBasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("%-v Unable to open base repository: %v", ctx.pr, err)
|
log.Error("%-v Unable to open base repository: %v", ctx.pr, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func checkPullRequestMergeableByTmpRepo(ctx context.Context, pr *issues_model.Pu
|
|||||||
}
|
}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(prCtx.tmpBasePath)
|
gitRepo, err := git.OpenRepositoryLocal(prCtx.tmpBasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("OpenRepository: %w", err)
|
return fmt.Errorf("OpenRepository: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,10 +213,10 @@ func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, re
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
relativePath := repo_model.RelativePath(u.Name, repoName)
|
codeRepo := repo_model.CodeRepoByName(u.Name, repoName)
|
||||||
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(relativePath))
|
exist, err := gitrepo.IsRepositoryExist(ctx, codeRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to check if %s exists. Error: %v", relativePath, err)
|
log.Error("Unable to check if repo %s/%s exists. Error: %v", u.Name, repoName, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !exist {
|
if !exist {
|
||||||
@@ -235,7 +235,7 @@ func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gitrepo.DeleteRepository(ctx, repo_model.StorageRepo(relativePath))
|
return gitrepo.DeleteRepository(ctx, codeRepo)
|
||||||
}
|
}
|
||||||
|
|
||||||
type unadoptedRepositories struct {
|
type unadoptedRepositories struct {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (t *TemporaryUploadRepository) Clone(ctx context.Context, branch string, ba
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("Clone: %w %s", err, stderr)
|
return fmt.Errorf("Clone: %w %s", err, stderr)
|
||||||
}
|
}
|
||||||
gitRepo, err := git.OpenRepository(t.basePath)
|
gitRepo, err := git.OpenRepositoryLocal(t.basePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ func (t *TemporaryUploadRepository) Init(ctx context.Context, objectFormatName s
|
|||||||
if err := git.InitRepository(ctx, t.basePath, false, objectFormatName); err != nil {
|
if err := git.InitRepository(ctx, t.basePath, false, objectFormatName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gitRepo, err := git.OpenRepository(t.basePath)
|
gitRepo, err := git.OpenRepositoryLocal(t.basePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func BenchmarkGetCommitGraph(b *testing.B) {
|
func BenchmarkGetCommitGraph(b *testing.B) {
|
||||||
currentRepo, err := git.OpenRepository(".")
|
currentRepo, err := git.OpenRepositoryLocal(".")
|
||||||
if err != nil || currentRepo == nil {
|
if err != nil || currentRepo == nil {
|
||||||
b.Error("Could not open repository")
|
b.Error("Could not open repository")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func cloneWiki(ctx context.Context, repo *repo_model.Repository, opts migration.
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitrepo.WriteCommitGraph(ctx, storageRepo); err != nil {
|
if err := git.WriteCommitGraph(ctx, storageRepo); err != nil {
|
||||||
cleanIncompleteWikiPath()
|
cleanIncompleteWikiPath()
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
|||||||
return repo, fmt.Errorf("clone error: %w", err)
|
return repo, fmt.Errorf("clone error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitrepo.WriteCommitGraph(ctx, repo); err != nil {
|
if err := git.WriteCommitGraph(ctx, repo); err != nil {
|
||||||
return repo, err
|
return repo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, na
|
|||||||
} else if has {
|
} else if has {
|
||||||
return repo_model.ErrRepoAlreadyExist{Uname: owner.Name, Name: name}
|
return repo_model.ErrRepoAlreadyExist{Uname: owner.Name, Name: name}
|
||||||
}
|
}
|
||||||
repo := repo_model.StorageRepo(repo_model.RelativePath(owner.Name, name))
|
repo := repo_model.CodeRepoByName(owner.Name, name)
|
||||||
isExist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
isExist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to check if repo %s/%s exists, error: %v", owner.Name, name, err)
|
log.Error("Unable to check if repo %s/%s exists, error: %v", owner.Name, name, err)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func isRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoNa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
repo := repo_model.StorageRepo(repo_model.RelativePath(u.Name, repoName))
|
repo := repo_model.CodeRepoByName(u.Name, repoName)
|
||||||
isExist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
isExist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
||||||
return has || isExist, err
|
return has || isExist, err
|
||||||
}
|
}
|
||||||
@@ -116,18 +116,19 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
|||||||
}
|
}
|
||||||
|
|
||||||
if repoRenamed {
|
if repoRenamed {
|
||||||
oldRelativePath, newRelativePath := repo_model.RelativePath(newOwnerName, repo.Name), repo_model.RelativePath(oldOwnerName, repo.Name)
|
// revert the rename
|
||||||
if err := gitrepo.RenameRepository(ctx, repo_model.StorageRepo(oldRelativePath), repo_model.StorageRepo(newRelativePath)); err != nil {
|
from := repo_model.CodeRepoByName(newOwnerName, repo.Name)
|
||||||
log.Error("Unable to move repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name,
|
to := repo_model.CodeRepoByName(oldOwnerName, repo.Name)
|
||||||
oldRelativePath, newRelativePath, err)
|
if err := gitrepo.RenameRepository(ctx, from, to); err != nil {
|
||||||
|
log.Error("Unable to revert repository %s/%s to %s/%s: %v", newOwnerName, repo.Name, oldOwnerName, repo.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if wikiRenamed {
|
if wikiRenamed {
|
||||||
oldRelativePath, newRelativePath := repo_model.RelativeWikiPath(newOwnerName, repo.Name), repo_model.RelativeWikiPath(oldOwnerName, repo.Name)
|
from := repo_model.WikiRepoByName(newOwnerName, repo.Name)
|
||||||
if err := gitrepo.RenameRepository(ctx, repo_model.StorageRepo(oldRelativePath), repo_model.StorageRepo(newRelativePath)); err != nil {
|
to := repo_model.WikiRepoByName(oldOwnerName, repo.Name)
|
||||||
log.Error("Unable to move wiki for repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name,
|
if err := gitrepo.RenameRepository(ctx, from, to); err != nil {
|
||||||
oldRelativePath, newRelativePath, err)
|
log.Error("Unable to revert wiki repository %s/%s to %s/%s: %v", newOwnerName, repo.Name, oldOwnerName, repo.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,19 +303,21 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rename remote repository to new path and delete local copy.
|
// Rename remote repository to new path and delete local copy.
|
||||||
oldRelativePath, newRelativePath := repo_model.RelativePath(oldOwner.Name, repo.Name), repo_model.RelativePath(newOwner.Name, repo.Name)
|
oldCodeRepo := repo_model.CodeRepoByName(oldOwner.Name, repo.Name)
|
||||||
if err := gitrepo.RenameRepository(ctx, repo_model.StorageRepo(oldRelativePath), repo_model.StorageRepo(newRelativePath)); err != nil {
|
newCodeRepo := repo_model.CodeRepoByName(newOwner.Name, repo.Name)
|
||||||
|
if err := gitrepo.RenameRepository(ctx, oldCodeRepo, newCodeRepo); err != nil {
|
||||||
return fmt.Errorf("rename repository directory: %w", err)
|
return fmt.Errorf("rename repository directory: %w", err)
|
||||||
}
|
}
|
||||||
repoRenamed = true
|
repoRenamed = true
|
||||||
|
|
||||||
// Rename remote wiki repository to new path and delete local copy.
|
// Rename remote wiki repository to new path and delete local copy.
|
||||||
wikiStorageRepo := repo_model.StorageRepo(repo_model.RelativeWikiPath(oldOwner.Name, repo.Name))
|
oldWikiRepo := repo_model.WikiRepoByName(oldOwner.Name, repo.Name)
|
||||||
if isExist, err := gitrepo.IsRepositoryExist(ctx, wikiStorageRepo); err != nil {
|
if isExist, err := gitrepo.IsRepositoryExist(ctx, oldWikiRepo); err != nil {
|
||||||
log.Error("Unable to check if wiki of repo %s/%s exists. Error: %v", oldOwner.Name, repo.Name, err)
|
log.Error("Unable to check if wiki of repo %s/%s exists. Error: %v", oldOwner.Name, repo.Name, err)
|
||||||
return err
|
return err
|
||||||
} else if isExist {
|
} else if isExist {
|
||||||
if err := gitrepo.RenameRepository(ctx, wikiStorageRepo, repo_model.StorageRepo(repo_model.RelativeWikiPath(newOwner.Name, repo.Name))); err != nil {
|
newWikiRepo := repo_model.WikiRepoByName(newOwner.Name, repo.Name)
|
||||||
|
if err := gitrepo.RenameRepository(ctx, oldWikiRepo, newWikiRepo); err != nil {
|
||||||
return fmt.Errorf("rename repository wiki: %w", err)
|
return fmt.Errorf("rename repository wiki: %w", err)
|
||||||
}
|
}
|
||||||
wikiRenamed = true
|
wikiRenamed = true
|
||||||
@@ -373,14 +376,14 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = gitrepo.RenameRepository(ctx, repo,
|
newCodeRepo := repo_model.CodeRepoByName(repo.OwnerName, newRepoName)
|
||||||
repo_model.StorageRepo(repo_model.RelativePath(repo.OwnerName, newRepoName))); err != nil {
|
if err = gitrepo.RenameRepository(ctx, repo, newCodeRepo); err != nil {
|
||||||
return fmt.Errorf("rename repository directory: %w", err)
|
return fmt.Errorf("rename repository directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if HasWiki(ctx, repo) {
|
if HasWiki(ctx, repo) {
|
||||||
if err = gitrepo.RenameRepository(ctx, repo.WikiStorageRepo(), repo_model.StorageRepo(
|
newWikiRepo := repo_model.WikiRepoByName(repo.OwnerName, newRepoName)
|
||||||
repo_model.RelativeWikiPath(repo.OwnerName, newRepoName))); err != nil {
|
if err = gitrepo.RenameRepository(ctx, repo.WikiStorageRepo(), newWikiRepo); err != nil {
|
||||||
return fmt.Errorf("rename repository wiki: %w", err)
|
return fmt.Errorf("rename repository wiki: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
|
|||||||
return fmt.Errorf("failed to clone repository: %s (%w)", repo.FullName(), err)
|
return fmt.Errorf("failed to clone repository: %s (%w)", repo.FullName(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(basePath)
|
gitRepo, err := git.OpenRepositoryLocal(basePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
|
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
|
||||||
return fmt.Errorf("failed to open new temporary repository in: %s %w", basePath, err)
|
return fmt.Errorf("failed to open new temporary repository in: %s %w", basePath, err)
|
||||||
@@ -282,7 +282,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
|
|||||||
return fmt.Errorf("failed to clone repository: %s (%w)", repo.FullName(), err)
|
return fmt.Errorf("failed to clone repository: %s (%w)", repo.FullName(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(basePath)
|
gitRepo, err := git.OpenRepositoryLocal(basePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
|
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
|
||||||
return fmt.Errorf("failed to open new temporary repository in: %s %w", basePath, err)
|
return fmt.Errorf("failed to open new temporary repository in: %s %w", basePath, err)
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ func TestPrepareWikiFileName_FirstPage(t *testing.T) {
|
|||||||
err := git.InitRepository(t.Context(), tmpDir, true, git.Sha1ObjectFormat.Name())
|
err := git.InitRepository(t.Context(), tmpDir, true, git.Sha1ObjectFormat.Name())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(tmpDir)
|
gitRepo, err := git.OpenRepositoryLocal(tmpDir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
Initial commit
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[core]
|
|
||||||
repositoryformatversion = 0
|
|
||||||
filemode = false
|
|
||||||
bare = false
|
|
||||||
logallrefupdates = true
|
|
||||||
symlinks = false
|
|
||||||
ignorecase = true
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit
|
|
||||||
cdaca8cf1d36e1e4e508a940f6e157e239beccfa cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 checkout: moving from master to branch1
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 branch: Created from cdaca8cf1d36e1e4e508a940f6e157e239beccfa
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Initial commit
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[core]
|
|
||||||
repositoryformatversion = 0
|
|
||||||
filemode = false
|
|
||||||
bare = false
|
|
||||||
logallrefupdates = true
|
|
||||||
symlinks = false
|
|
||||||
ignorecase = true
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit
|
|
||||||
cdaca8cf1d36e1e4e508a940f6e157e239beccfa cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 checkout: moving from master to branch1
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 branch: Created from cdaca8cf1d36e1e4e508a940f6e157e239beccfa
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Initial commit
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[core]
|
|
||||||
repositoryformatversion = 0
|
|
||||||
filemode = false
|
|
||||||
bare = false
|
|
||||||
logallrefupdates = true
|
|
||||||
symlinks = false
|
|
||||||
ignorecase = true
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit
|
|
||||||
cdaca8cf1d36e1e4e508a940f6e157e239beccfa cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 checkout: moving from master to branch1
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 branch: Created from cdaca8cf1d36e1e4e508a940f6e157e239beccfa
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cbff181af4c9c7fee3cf6c106699e07d9a3f54e6 Gitea <gitea@fake.local> 1688672318 +0200
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 1978192d98bb1b65e11c2cf37da854fbf94bffd6 Gitea <gitea@fake.local> 1688672383 +0200 push
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 cbff181af4c9c7fee3cf6c106699e07d9a3f54e6 root <sauer.sebastian@gmail.com> 1688672317 +0200 push
|
|
||||||
@@ -811,7 +811,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(dstPath)
|
gitRepo, err := git.OpenRepositoryLocal(dstPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func TestAgitPullPush(t *testing.T) {
|
|||||||
dstPath := t.TempDir()
|
dstPath := t.TempDir()
|
||||||
doGitClone(dstPath, u)(t)
|
doGitClone(dstPath, u)(t)
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(dstPath)
|
gitRepo, err := git.OpenRepositoryLocal(dstPath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ func TestAgitReviewStaleness(t *testing.T) {
|
|||||||
dstPath := t.TempDir()
|
dstPath := t.TempDir()
|
||||||
doGitClone(dstPath, u)(t)
|
doGitClone(dstPath, u)(t)
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(dstPath)
|
gitRepo, err := git.OpenRepositoryLocal(dstPath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gi
|
|||||||
|
|
||||||
doGitAddRemote(gitPath, "origin", u)(t)
|
doGitAddRemote(gitPath, "origin", u)(t)
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(gitPath)
|
gitRepo, err := git.OpenRepositoryLocal(gitPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user