mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-08 22:13:26 +09:00
Compare commits
4
Commits
6ac19f6226
...
7065637e61
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7065637e61 | ||
|
|
86a3048247 | ||
|
|
e992b0a7cc | ||
|
|
c4fc4d363b |
+1
-1
@@ -198,7 +198,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
defer func() {
|
||||
_ = dbDump.Close()
|
||||
if err := util.Remove(dbDump.Name()); err != nil {
|
||||
if err := os.Remove(dbDump.Name()); err != nil {
|
||||
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
|
||||
}
|
||||
}()
|
||||
|
||||
+1
-2
@@ -18,7 +18,6 @@ import (
|
||||
"gitea.dev/modules/public"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/templates"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
@@ -255,7 +254,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error {
|
||||
} else if !fi.Mode().IsRegular() {
|
||||
return fmt.Errorf("%s already exists, but it's not a regular file", dest)
|
||||
} else if rename {
|
||||
if err := util.Rename(dest, dest+".bak"); err != nil {
|
||||
if err := os.Rename(dest, dest+".bak"); err != nil {
|
||||
return fmt.Errorf("error creating backup for %s: %w", dest, err)
|
||||
}
|
||||
// Attempt to respect file permissions mask (even if user:group will be set anew)
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
package v1_10
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
func DeleteOrphanedAttachments(x base.EngineMigration) error {
|
||||
@@ -52,7 +52,7 @@ func DeleteOrphanedAttachments(x base.EngineMigration) error {
|
||||
|
||||
for _, attachment := range attachments {
|
||||
uuid := attachment.UUID
|
||||
if err := util.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
|
||||
if err := os.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -30,7 +30,7 @@ func RemoveAttachmentMissedRepo(x base.EngineMigration) error {
|
||||
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
uuid := attachments[i].UUID
|
||||
if err = util.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
|
||||
if err = os.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
|
||||
log.Warn("Unable to remove attachment file by UUID %s: %v", uuid, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"gitea.dev/modules/container"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
func RenameExistingUserAvatarName(x base.EngineMigration) error {
|
||||
@@ -110,8 +109,8 @@ func RenameExistingUserAvatarName(x base.EngineMigration) error {
|
||||
log.Info("Deleting %d old avatars ...", deleteCount)
|
||||
i := 0
|
||||
for file := range deleteList {
|
||||
if err := util.Remove(file); err != nil {
|
||||
log.Warn("util.Remove: %v", err)
|
||||
if err := os.Remove(file); err != nil {
|
||||
log.Warn("Failed to remove avatar %s: %v", file, err)
|
||||
}
|
||||
i++
|
||||
select {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"maps"
|
||||
"net"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -564,16 +563,6 @@ func (repo *Repository) IsGenerated() bool {
|
||||
return repo.TemplateID != 0
|
||||
}
|
||||
|
||||
// RepoPath returns repository path by given user and repository name.
|
||||
func RepoPath(userName, repoName string) string { //revive:disable-line:exported
|
||||
return filepath.Join(setting.RepoRootPath, filepath.Clean(strings.ToLower(userName)), filepath.Clean(strings.ToLower(repoName)+".git"))
|
||||
}
|
||||
|
||||
// RepoPath returns the repository path
|
||||
func (repo *Repository) RepoPath() string {
|
||||
return RepoPath(repo.OwnerName, repo.Name)
|
||||
}
|
||||
|
||||
// Link returns the repository relative url
|
||||
func (repo *Repository) Link() string {
|
||||
return setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
|
||||
|
||||
@@ -15,8 +15,8 @@ func repoCodeGitRepoManagedID(repoID int64) string {
|
||||
|
||||
func (repo *Repository) CodeStorageRepo() gitrepo.RepositoryFacade {
|
||||
id := repoCodeGitRepoManagedID(repo.ID)
|
||||
repoPath := gitrepo.RepoCodeGitRepoRelativePath(repo.OwnerName, repo.Name)
|
||||
return gitrepo.RepositoryManaged(id, repoPath)
|
||||
relPath := gitrepo.RepoCodeGitRepoRelativePath(repo.OwnerName, repo.Name)
|
||||
return gitrepo.RepositoryManaged(id, relPath)
|
||||
}
|
||||
|
||||
func (repo *Repository) GitRepoLocation() string {
|
||||
|
||||
@@ -127,7 +127,7 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) {
|
||||
|
||||
for _, upload := range uploads {
|
||||
localPath := upload.LocalPath()
|
||||
if err := util.Remove(localPath); err != nil {
|
||||
if err := util.RemoveWithRetry(localPath); err != nil {
|
||||
// just continue, don't fail the whole operation if a file is missing (removed by others)
|
||||
log.Error("unable to remove upload file %s: %v", localPath, err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"mime"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -993,11 +992,6 @@ func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, er
|
||||
Find(&users)
|
||||
}
|
||||
|
||||
// UserPath returns the path absolute path of user repositories.
|
||||
func UserPath(userName string) string { //revive:disable-line:exported
|
||||
return filepath.Join(setting.RepoRootPath, filepath.Clean(strings.ToLower(userName)))
|
||||
}
|
||||
|
||||
// GetUserByID returns the user object by given ID if exists.
|
||||
func GetUserByID(ctx context.Context, id int64) (*User, error) {
|
||||
u := new(User)
|
||||
|
||||
@@ -75,7 +75,22 @@ func (w *SingleWorkflow) SetJob(id string, job *Job) error {
|
||||
}
|
||||
|
||||
func (w *SingleWorkflow) Marshal() ([]byte, error) {
|
||||
return yaml.Marshal(w)
|
||||
// Encode with the same indentation SetJob uses (2). yaml.Marshal's default
|
||||
// indentation (4) makes the encoder emit multi-line block scalars (e.g. a
|
||||
// `run:` step that begins with blank lines) with a wrong explicit indentation
|
||||
// indicator (`run: |4`) that then fails to re-parse, which silently strands
|
||||
// the job during concurrency evaluation. Keeping both encoders at indent 2
|
||||
// makes the serialized single workflow round-trip.
|
||||
var buf bytes.Buffer
|
||||
enc := yaml.NewEncoder(&buf)
|
||||
enc.SetIndent(2)
|
||||
if err := enc.Encode(w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := enc.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package jobparser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// A step whose `run:` block starts with blank lines must still survive the
|
||||
// Parse -> SingleWorkflow.Marshal -> Parse round-trip. Previously Marshal used a
|
||||
// different indentation than SetJob, which made the encoder emit the block scalar
|
||||
// with a wrong explicit indentation indicator (`run: |4`) that no longer parsed;
|
||||
// the job then stayed silently blocked during concurrency evaluation.
|
||||
func TestSingleWorkflowRoundTripRunBlockLeadingBlankLines(t *testing.T) {
|
||||
const wf = `name: demo
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
app_name:
|
||||
type: string
|
||||
required: true
|
||||
jobs:
|
||||
build:
|
||||
name: build
|
||||
env:
|
||||
IMAGE_TAG: ${{ inputs.app_name }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- if: ${{ inputs.app_name != '' }}
|
||||
name: packages
|
||||
run: |
|
||||
|
||||
|
||||
echo start
|
||||
echo done
|
||||
`
|
||||
sws, err := Parse([]byte(wf))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sws, 1)
|
||||
|
||||
// pin the original run block as the baseline
|
||||
_, origJob := sws[0].Job()
|
||||
require.Len(t, origJob.Steps, 1)
|
||||
const wantRun = "\n\necho start\necho done\n"
|
||||
require.Equal(t, wantRun, origJob.Steps[0].Run)
|
||||
|
||||
payload, err := sws[0].Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
// the serialized single workflow must be parseable again -- this is what the
|
||||
// server does in EvaluateJobConcurrencyFillModel -> ParseJob. Before the fix
|
||||
// Marshal emitted `run: |4`, which failed here and left the job blocked.
|
||||
roundTripped, err := Parse(payload)
|
||||
require.NoError(t, err, "serialized single workflow must round-trip; got payload:\n%s", payload)
|
||||
require.Len(t, roundTripped, 1)
|
||||
|
||||
// the round-trip must preserve the run block byte-for-byte
|
||||
_, gotJob := roundTripped[0].Job()
|
||||
require.Len(t, gotJob.Steps, 1)
|
||||
require.Equal(t, wantRun, gotJob.Steps[0].Run, "round-trip must preserve run content; got payload:\n%s", payload)
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
func testRun(m *testing.M) error {
|
||||
@@ -18,7 +17,7 @@ func testRun(m *testing.M) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temp dir: %w", err)
|
||||
}
|
||||
defer util.RemoveAll(gitHomePath)
|
||||
defer os.RemoveAll(gitHomePath)
|
||||
setting.Git.HomePath = gitHomePath
|
||||
|
||||
if err = git.InitFull(); err != nil {
|
||||
|
||||
@@ -25,12 +25,16 @@ type catFileBatchCommunicator struct {
|
||||
reqWriter io.Writer
|
||||
respReader *bufio.Reader
|
||||
debugGitCmd *gitcmd.Command
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
func (b *catFileBatchCommunicator) Close(err ...error) {
|
||||
if fn := b.closeFunc.Swap(nil); fn != nil {
|
||||
(*fn)(util.OptionalArg(err))
|
||||
}
|
||||
// make sure the git process has fully exited before we return from Close()
|
||||
// otherwise, the opened files will block the directory renaming (rename a repo) on Windows
|
||||
<-b.closed
|
||||
}
|
||||
|
||||
// newCatFileBatch opens git cat-file --batch/--batch-check/--batch-command command and prepares the stdin/stdout pipes for communication.
|
||||
@@ -41,6 +45,7 @@ func newCatFileBatch(ctx context.Context, repo RepositoryFacade, cmdCatFile *git
|
||||
debugGitCmd: cmdCatFile,
|
||||
reqWriter: stdinWriter,
|
||||
respReader: bufio.NewReaderSize(stdoutReader, 32*1024), // use a buffered reader for rich operations
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
ret.closeFunc.Store(new(func(err error) {
|
||||
ctxCancel(err)
|
||||
@@ -52,6 +57,7 @@ func newCatFileBatch(ctx context.Context, repo RepositoryFacade, cmdCatFile *git
|
||||
log.Error("Unable to start git command %v: %v", cmdCatFile.LogString(), err)
|
||||
// ideally here it should return the error, but it would require refactoring all callers
|
||||
// so just return a dummy communicator that does nothing, almost the same behavior as before, not bad
|
||||
close(ret.closed)
|
||||
ret.Close(err)
|
||||
return ret
|
||||
}
|
||||
@@ -61,6 +67,7 @@ func newCatFileBatch(ctx context.Context, repo RepositoryFacade, cmdCatFile *git
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
log.Error("cat-file --batch command failed in repo %s, error: %v", repo.LogString(), err)
|
||||
}
|
||||
close(ret.closed)
|
||||
ret.Close(err)
|
||||
}()
|
||||
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
|
||||
package gitrepo
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
func RepoCodeGitRepoRelativePath(ownerName, repoName string) string {
|
||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git"
|
||||
return util.PathJoinRelX(strings.ToLower(ownerName), strings.ToLower(repoName)+".git")
|
||||
}
|
||||
|
||||
func RepoWikiGitRepoRelativePath(ownerName, repoName string) string {
|
||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
|
||||
return util.PathJoinRelX(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.
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
package gitrepo
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"gitea.dev/modules/setting"
|
||||
@@ -39,6 +42,13 @@ func RepoLocalPath(repo RepositoryFacade) string {
|
||||
return setting.RepoRootPath + string(filepath.Separator) + filepath.FromSlash(repoLoc)
|
||||
}
|
||||
|
||||
func UserLocalPath(userName string) string {
|
||||
if setting.RepoRootPath == "" {
|
||||
panic("repo root path is not initialized")
|
||||
}
|
||||
return filepath.Join(setting.RepoRootPath, filepath.Clean(strings.ToLower(userName)))
|
||||
}
|
||||
|
||||
func repoLogNameByLocation(loc string) string {
|
||||
t := filepath.FromSlash(loc)
|
||||
// hide the parent paths, then the name should be safe for end users
|
||||
@@ -100,3 +110,7 @@ func (r *repositoryManaged) GitRepoLocation() string {
|
||||
func RepositoryManaged(id, loc string) RepositoryFacade {
|
||||
return &repositoryManaged{id: id, loc: filepath.Clean(loc)}
|
||||
}
|
||||
|
||||
func RepoLocalFS(repo RepositoryFacade) fs.FS {
|
||||
return os.DirFS(RepoLocalPath(repo))
|
||||
}
|
||||
|
||||
+3
-8
@@ -72,16 +72,11 @@ func (h *Hook) Name() string {
|
||||
// Update updates hook settings.
|
||||
func (h *Hook) Update() error {
|
||||
if len(strings.TrimSpace(h.Content)) == 0 {
|
||||
exist, err := util.IsExist(h.path)
|
||||
if err != nil {
|
||||
// empty content means to remove the file
|
||||
err := util.RemoveWithRetry(h.path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
err := util.Remove(h.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
h.IsActive = false
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ func createDelegateHooks(hookDir string) (err error) {
|
||||
}
|
||||
|
||||
// WARNING: This will override all old server-side hooks
|
||||
if err = util.Remove(oldHookPath); err != nil && !os.IsNotExist(err) {
|
||||
if err = util.RemoveWithRetry(oldHookPath); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %w ", oldHookPath, err)
|
||||
}
|
||||
if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0o777); err != nil {
|
||||
@@ -135,7 +135,7 @@ func createDelegateHooks(hookDir string) (err error) {
|
||||
return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err)
|
||||
}
|
||||
|
||||
if err = util.Remove(newHookPath); err != nil && !os.IsNotExist(err) {
|
||||
if err = util.RemoveWithRetry(newHookPath); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %w", newHookPath, err)
|
||||
}
|
||||
if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0o777); err != nil {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -23,7 +22,7 @@ func IsRepositoryExist(ctx context.Context, repo RepositoryFacade) (bool, error)
|
||||
// DeleteRepository deletes the repository directory from the disk, it will return
|
||||
// nil if the repository does not exist.
|
||||
func DeleteRepository(ctx context.Context, repo RepositoryFacade) error {
|
||||
return util.RemoveAll(gitrepo.RepoLocalPath(repo))
|
||||
return util.RemoveAllWithRetry(gitrepo.RepoLocalPath(repo))
|
||||
}
|
||||
|
||||
// RenameRepository renames a repository's name on disk
|
||||
@@ -33,7 +32,7 @@ func RenameRepository(ctx context.Context, repo, newRepo RepositoryFacade) error
|
||||
return fmt.Errorf("Failed to create dir %s: %w", filepath.Dir(dstDir), err)
|
||||
}
|
||||
|
||||
if err := util.Rename(gitrepo.RepoLocalPath(repo), dstDir); err != nil {
|
||||
if err := util.RenameWithRetry(gitrepo.RepoLocalPath(repo), dstDir); err != nil {
|
||||
return fmt.Errorf("rename repository directory: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -43,10 +42,6 @@ func InitRepository(ctx context.Context, repo RepositoryFacade, objectFormatName
|
||||
return InitRepositoryLocal(ctx, gitrepo.RepoLocalPath(repo), true, objectFormatName)
|
||||
}
|
||||
|
||||
func GetRepoFS(repo RepositoryFacade) fs.FS {
|
||||
return os.DirFS(gitrepo.RepoLocalPath(repo))
|
||||
}
|
||||
|
||||
func IsRepoFileExist(ctx context.Context, repo RepositoryFacade, relativeFilePath string) (bool, error) {
|
||||
absoluteFilePath := filepath.Join(gitrepo.RepoLocalPath(repo), relativeFilePath)
|
||||
return util.IsExist(absoluteFilePath)
|
||||
@@ -59,7 +54,7 @@ func IsRepoDirExist(ctx context.Context, repo RepositoryFacade, relativeDirPath
|
||||
|
||||
func RemoveRepoFileOrDir(ctx context.Context, repo RepositoryFacade, relativeFileOrDirPath string) error {
|
||||
absoluteFilePath := filepath.Join(gitrepo.RepoLocalPath(repo), relativeFileOrDirPath)
|
||||
return util.Remove(absoluteFilePath)
|
||||
return util.RemoveWithRetry(absoluteFilePath)
|
||||
}
|
||||
|
||||
func CreateRepoFile(ctx context.Context, repo RepositoryFacade, relativeFilePath string) (io.WriteCloser, error) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
)
|
||||
|
||||
type lineCountWriter struct {
|
||||
@@ -113,7 +114,7 @@ func (repo *Repository) GetFilesChangedBetween(ctx context.Context, base, head s
|
||||
// ReadPatchCommit will check if a diff patch exists and return stats
|
||||
func (repo *Repository) ReadPatchCommit(prID int64) (commitSHA string, err error) {
|
||||
// Migrated repositories download patches to "pulls" location
|
||||
repoFS := GetRepoFS(repo)
|
||||
repoFS := gitrepo.RepoLocalFS(repo)
|
||||
loadPatch, err := repoFS.Open(fmt.Sprintf("pulls/%d.patch", prID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -121,8 +120,7 @@ func getProvidedFDs() (savedErr error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If needed we can handle packetconns here.
|
||||
savedErr = fmt.Errorf("Error getting provided socket fd %d: %w", i, err)
|
||||
savedErr = fmt.Errorf("error getting provided socket fd %d: %w", i, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
@@ -228,8 +226,8 @@ func GetListenerUnix(network string, address *net.UnixAddr) (*net.UnixListener,
|
||||
}
|
||||
|
||||
// make a fresh listener
|
||||
if err := util.Remove(address.Name); err != nil && !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("Failed to remove unix socket %s: %w", address.Name, err)
|
||||
if err := os.Remove(address.Name); err != nil && !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("failed to remove unix socket %s: %w", address.Name, err)
|
||||
}
|
||||
|
||||
l, err := net.ListenUnix(network, address)
|
||||
@@ -239,7 +237,7 @@ func GetListenerUnix(network string, address *net.UnixAddr) (*net.UnixListener,
|
||||
|
||||
fileMode := os.FileMode(setting.UnixSocketPermission)
|
||||
if err = os.Chmod(address.Name, fileMode); err != nil {
|
||||
return nil, fmt.Errorf("Failed to set permission of unix socket to %s: %w", fileMode.String(), err)
|
||||
return nil, fmt.Errorf("failed to set permission of unix socket to %s: %w", fileMode.String(), err)
|
||||
}
|
||||
|
||||
activeListeners = append(activeListeners, l)
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
unicode_tokenizer "github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
||||
@@ -40,14 +39,14 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) {
|
||||
if metadata.Version < latestVersion {
|
||||
// the indexer is using a previous version, so we should delete it and
|
||||
// re-populate
|
||||
return nil, metadata.Version, util.RemoveAll(path)
|
||||
return nil, metadata.Version, os.RemoveAll(path)
|
||||
}
|
||||
|
||||
index, err := bleve.Open(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, upsidedown.IncompatibleVersion) {
|
||||
log.Warn("Indexer was built with a previous version of bleve, deleting and rebuilding")
|
||||
return nil, 0, util.RemoveAll(path)
|
||||
return nil, 0, os.RemoveAll(path)
|
||||
}
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ import (
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
// settings
|
||||
const IsWindows = runtime.GOOS == "windows"
|
||||
|
||||
var (
|
||||
// AppVer is the version of the current build of Gitea. It is set in main.go from main.Version.
|
||||
AppVer string
|
||||
@@ -27,7 +28,6 @@ var (
|
||||
AppStartTime time.Time
|
||||
|
||||
CfgProvider ConfigProvider
|
||||
IsWindows bool
|
||||
|
||||
// IsInTesting indicates whether the testing is running (unit test or integration test). It can be used for:
|
||||
// * Skip nonsense error logs during testing caused by unreliable code (TODO: this is only a temporary solution, we should make the test code more reliable)
|
||||
@@ -37,7 +37,6 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
IsWindows = runtime.GOOS == "windows"
|
||||
if AppVer == "" {
|
||||
AppVer = "dev"
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error)
|
||||
tmpRemoved := false
|
||||
defer func() {
|
||||
if !tmpRemoved {
|
||||
_ = util.Remove(tmp.Name())
|
||||
_ = util.RemoveWithRetry(tmp.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -97,7 +97,7 @@ func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := util.Rename(tmp.Name(), p); err != nil {
|
||||
if err := util.RenameWithRetry(tmp.Name(), p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Golang's tmp file (os.CreateTemp) always have 0o600 mode, so we need to change the file to follow the umask (as what Create/MkDir does)
|
||||
@@ -118,7 +118,7 @@ func (l *LocalStorage) Stat(path string) (os.FileInfo, error) {
|
||||
|
||||
func (l *LocalStorage) deleteEmptyParentDirs(localFullPath string) {
|
||||
for parent := filepath.Dir(localFullPath); len(parent) > len(l.dir); parent = filepath.Dir(parent) {
|
||||
if err := os.Remove(parent); err != nil {
|
||||
if err := util.RemoveWithRetry(parent); err != nil && !os.IsNotExist(err) {
|
||||
// since the target file has been deleted, parent dir error is not related to the file deletion itself.
|
||||
break
|
||||
}
|
||||
@@ -128,7 +128,7 @@ func (l *LocalStorage) deleteEmptyParentDirs(localFullPath string) {
|
||||
// Delete deletes the file in storage and removes the empty parent directories (if possible)
|
||||
func (l *LocalStorage) Delete(path string) error {
|
||||
localFullPath := l.buildLocalPath(path)
|
||||
err := util.Remove(localFullPath)
|
||||
err := util.RemoveWithRetry(localFullPath)
|
||||
l.deleteEmptyParentDirs(localFullPath)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (td *TempDir) MkdirTempRandom(elems ...string) (string, func(), error) {
|
||||
return "", nil, err
|
||||
}
|
||||
return dir, func() {
|
||||
if err := util.RemoveAll(dir); err != nil {
|
||||
if err := util.RemoveAllWithRetry(dir); err != nil {
|
||||
log.Error("Failed to remove temp directory %s: %v", dir, err)
|
||||
}
|
||||
}, nil
|
||||
@@ -75,7 +75,7 @@ func (td *TempDir) CreateTempFileRandom(elems ...string) (*os.File, func(), erro
|
||||
filename := f.Name()
|
||||
return f, func() {
|
||||
_ = f.Close()
|
||||
if err := util.Remove(filename); err != nil {
|
||||
if err := util.RemoveWithRetry(filename); err != nil {
|
||||
log.Error("Unable to remove temporary file: %s: Error: %v", filename, err)
|
||||
}
|
||||
}, err
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// On Windows, when a file or directory is in use (opened), the file or directory is not able to be removed or renamed.
|
||||
// When renaming or removing a local git repository directory:
|
||||
// * the "cat-batch" git process might be running in a goroutine
|
||||
// * there can be a data-race between the "cat-batch" git process cancel+exit and the repo rename
|
||||
// So we need to retry the rename/remove operation for a few times when the "cat-batch" git process is exiting.
|
||||
// ref: https://github.com/go-gitea/gitea/issues/16427, https://github.com/go-gitea/gitea/issues/16475, https://github.com/go-gitea/gitea/pull/16479
|
||||
// Also some similar problems when removing a file, e.g.: https://github.com/go-gitea/gitea/issues/12339
|
||||
//
|
||||
// Usually, if no concurrent access to a file, use "os.Xxx", otherwise, use "util.XxxWithRetry"
|
||||
|
||||
func retryWhenFileBusyInternal(count int, delay time.Duration, f func() error) (err error) {
|
||||
const errWindowsSharingViolationError = syscall.Errno(32)
|
||||
for range count {
|
||||
err = f()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
isErrBusy := errors.Is(err, syscall.EBUSY) || errors.Is(err, syscall.ENOTEMPTY) || errors.Is(err, syscall.EPERM) || errors.Is(err, syscall.EMFILE) || errors.Is(err, syscall.ENFILE)
|
||||
isErrBusy = isErrBusy || (isOSWindows && errors.Is(err, errWindowsSharingViolationError))
|
||||
if !isErrBusy {
|
||||
break
|
||||
}
|
||||
time.Sleep(delay)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func retryWhenFileBusy(f func() error) (err error) {
|
||||
return retryWhenFileBusyInternal(5, 100*time.Millisecond, f)
|
||||
}
|
||||
|
||||
func RemoveWithRetry(path string) error {
|
||||
return retryWhenFileBusy(func() error {
|
||||
return os.Remove(path)
|
||||
})
|
||||
}
|
||||
|
||||
func RemoveAllWithRetry(path string) error {
|
||||
return retryWhenFileBusy(func() error {
|
||||
return os.RemoveAll(path)
|
||||
})
|
||||
}
|
||||
|
||||
func RenameWithRetry(oldpath, newpath string) error {
|
||||
return retryWhenFileBusy(func() error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRetryWhenFileBusy(t *testing.T) {
|
||||
var callCount int
|
||||
testErrPath := &os.PathError{Op: "test", Path: "test", Err: syscall.EBUSY}
|
||||
fn := func() error {
|
||||
if callCount == 2 {
|
||||
return nil
|
||||
}
|
||||
callCount++
|
||||
return testErrPath
|
||||
}
|
||||
|
||||
callCount = 0
|
||||
err := retryWhenFileBusyInternal(1, time.Millisecond, fn)
|
||||
assert.Equal(t, testErrPath, err)
|
||||
assert.Equal(t, 1, callCount)
|
||||
|
||||
callCount = 0
|
||||
err = retryWhenFileBusyInternal(10, time.Millisecond, fn)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, callCount)
|
||||
|
||||
callCount = 0
|
||||
err = retryWhenFileBusyInternal(10, time.Millisecond, func() error {
|
||||
callCount++
|
||||
return io.ErrUnexpectedEOF
|
||||
})
|
||||
assert.Equal(t, io.ErrUnexpectedEOF, err)
|
||||
assert.Equal(t, 1, callCount)
|
||||
}
|
||||
+22
-24
@@ -10,8 +10,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -77,7 +75,7 @@ const filepathSeparator = string(os.PathSeparator)
|
||||
func FilePathJoinAbs(base string, sub ...string) string {
|
||||
// POSIX filesystem can have `\` in file names. Windows: `\` and `/` are both used for path separators
|
||||
// to keep the behavior consistent, we do not allow `\` in file names, replace all `\` with `/`
|
||||
if !isOSWindows() {
|
||||
if !isOSWindows {
|
||||
base = strings.ReplaceAll(base, "\\", filepathSeparator)
|
||||
}
|
||||
if !filepath.IsAbs(base) {
|
||||
@@ -94,7 +92,7 @@ func FilePathJoinAbs(base string, sub ...string) string {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if isOSWindows() {
|
||||
if isOSWindows {
|
||||
elems = append(elems, filepath.Clean(filepathSeparator+s))
|
||||
} else {
|
||||
elems = append(elems, filepath.Clean(filepathSeparator+strings.ReplaceAll(s, "\\", filepathSeparator)))
|
||||
@@ -245,30 +243,30 @@ func ListDirRecursively(rootDir string, opts *ListDirOptions) (res []string, err
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func isOSWindows() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
func fileURLToPathInternal(u *url.URL, isWindows bool) (string, error) {
|
||||
if u.Scheme != "file" {
|
||||
return "", errors.New("URL scheme is not 'file': " + u.String())
|
||||
}
|
||||
if !isWindows {
|
||||
return u.Path, nil
|
||||
}
|
||||
|
||||
var driveLetterRegexp = regexp.MustCompile("/[A-Za-z]:/")
|
||||
// If it is a Windows absolute path with drive letter "/C:/dir", strip off the leading slash.
|
||||
if !strings.HasPrefix(u.Path, "/") || len(u.Path) < 3 {
|
||||
return u.Path, nil
|
||||
}
|
||||
winPath := u.Path[1:]
|
||||
first := winPath[0]
|
||||
if ('a' <= first && first <= 'z' || 'A' <= first && first <= 'Z') && winPath[1] == ':' {
|
||||
return winPath, nil
|
||||
}
|
||||
return u.Path, nil
|
||||
}
|
||||
|
||||
// FileURLToPath extracts the path information from a file://... url.
|
||||
// It returns an error only if the URL is not a file URL.
|
||||
func FileURLToPath(u *url.URL) (string, error) {
|
||||
if u.Scheme != "file" {
|
||||
return "", errors.New("URL scheme is not 'file': " + u.String())
|
||||
}
|
||||
|
||||
path := u.Path
|
||||
|
||||
if !isOSWindows() {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// If it looks like there's a Windows drive letter at the beginning, strip off the leading slash.
|
||||
if driveLetterRegexp.MatchString(path) {
|
||||
return path[1:], nil
|
||||
}
|
||||
return path, nil
|
||||
return fileURLToPathInternal(u, isOSWindows)
|
||||
}
|
||||
|
||||
// HomeDir returns path of '~'(in Linux) on Windows,
|
||||
@@ -277,7 +275,7 @@ func HomeDir() (home string, err error) {
|
||||
// TODO: some users run Gitea with mismatched uid and "HOME=xxx" (they set HOME=xxx by environment manually)
|
||||
// TODO: when running gitea as a sub command inside git, the HOME directory is not the user's home directory
|
||||
// so at the moment we can not use `user.Current().HomeDir`
|
||||
if isOSWindows() {
|
||||
if isOSWindows {
|
||||
home = os.Getenv("USERPROFILE")
|
||||
if home == "" {
|
||||
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||
|
||||
+22
-20
@@ -7,7 +7,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -18,43 +17,46 @@ func TestFileURLToPath(t *testing.T) {
|
||||
cases := []struct {
|
||||
url string
|
||||
expected string
|
||||
haserror bool
|
||||
invalid bool
|
||||
windows bool
|
||||
}{
|
||||
// case 0
|
||||
{
|
||||
url: "",
|
||||
haserror: true,
|
||||
url: "",
|
||||
invalid: true,
|
||||
},
|
||||
// case 1
|
||||
{
|
||||
url: "http://test.io",
|
||||
haserror: true,
|
||||
url: "http://test.io",
|
||||
invalid: true,
|
||||
},
|
||||
// case 2
|
||||
{
|
||||
url: "file:///path",
|
||||
expected: "/path",
|
||||
},
|
||||
// case 3
|
||||
{
|
||||
url: "file:///C:/path",
|
||||
expected: "C:/path",
|
||||
windows: true,
|
||||
},
|
||||
{
|
||||
url: "file:///z:/path",
|
||||
expected: "z:/path",
|
||||
windows: true,
|
||||
},
|
||||
{
|
||||
url: "file:///path",
|
||||
expected: "/path",
|
||||
windows: true,
|
||||
},
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
if c.windows && runtime.GOOS != "windows" {
|
||||
continue
|
||||
}
|
||||
for _, c := range cases {
|
||||
u, _ := url.Parse(c.url)
|
||||
p, err := FileURLToPath(u)
|
||||
if c.haserror {
|
||||
assert.Error(t, err, "case %d: should return error", n)
|
||||
p, err := fileURLToPathInternal(u, c.windows)
|
||||
if c.invalid {
|
||||
assert.Error(t, err, "case %s: should return error", c.url)
|
||||
} else {
|
||||
assert.NoError(t, err, "case %d: should not return error", n)
|
||||
assert.Equal(t, c.expected, p, "case %d: should be equal", n)
|
||||
assert.NoError(t, err, "case %s: should not return error", c.url)
|
||||
assert.Equal(t, c.expected, p, "case %s: should be equal", c.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,7 +182,7 @@ func TestCleanPath(t *testing.T) {
|
||||
}
|
||||
|
||||
// for POSIX only, but the result is similar on Windows, because the first element must be an absolute path
|
||||
if isOSWindows() {
|
||||
if isOSWindows {
|
||||
cases = []struct {
|
||||
elems []string
|
||||
expected string
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const windowsSharingViolationError syscall.Errno = 32
|
||||
|
||||
// Remove removes the named file or (empty) directory with at most 5 attempts.
|
||||
func Remove(name string) error {
|
||||
var err error
|
||||
for range 5 {
|
||||
err = os.Remove(name)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
unwrapped := err.(*os.PathError).Err
|
||||
if unwrapped == syscall.EBUSY || unwrapped == syscall.ENOTEMPTY || unwrapped == syscall.EPERM || unwrapped == syscall.EMFILE || unwrapped == syscall.ENFILE {
|
||||
// try again
|
||||
<-time.After(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
|
||||
// try again
|
||||
<-time.After(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
if unwrapped == syscall.ENOENT {
|
||||
// it's already gone
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveAll removes the named file or (empty) directory with at most 5 attempts.
|
||||
func RemoveAll(name string) error {
|
||||
var err error
|
||||
for range 5 {
|
||||
err = os.RemoveAll(name)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
unwrapped := err.(*os.PathError).Err
|
||||
if unwrapped == syscall.EBUSY || unwrapped == syscall.ENOTEMPTY || unwrapped == syscall.EPERM || unwrapped == syscall.EMFILE || unwrapped == syscall.ENFILE {
|
||||
// try again
|
||||
<-time.After(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
|
||||
// try again
|
||||
<-time.After(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
if unwrapped == syscall.ENOENT {
|
||||
// it's already gone
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename renames (moves) oldpath to newpath with at most 5 attempts.
|
||||
func Rename(oldpath, newpath string) error {
|
||||
var err error
|
||||
for i := range 5 {
|
||||
err = os.Rename(oldpath, newpath)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
unwrapped := err.(*os.LinkError).Err
|
||||
if unwrapped == syscall.EBUSY || unwrapped == syscall.ENOTEMPTY || unwrapped == syscall.EPERM || unwrapped == syscall.EMFILE || unwrapped == syscall.ENFILE {
|
||||
// try again
|
||||
<-time.After(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
|
||||
// try again
|
||||
<-time.After(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
if i == 0 && os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if unwrapped == syscall.ENOENT {
|
||||
// it's already gone
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -152,7 +152,7 @@ func (rfw *RotatingFileWriter) DoRotate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.Rename(fd.Name(), fname); err != nil {
|
||||
if err := util.RenameWithRetry(fd.Name(), fname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -203,12 +203,12 @@ func compressOldFile(fname string, compressionLevel int) error {
|
||||
if err != nil {
|
||||
_ = zw.Close()
|
||||
_ = fw.Close()
|
||||
_ = util.Remove(fname + ".gz")
|
||||
_ = util.RemoveWithRetry(fname + ".gz")
|
||||
return fmt.Errorf("compressOldFile: failed to write to gz file: %w", err)
|
||||
}
|
||||
_ = reader.Close()
|
||||
|
||||
err = util.Remove(fname)
|
||||
err = util.RemoveWithRetry(fname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compressOldFile: failed to delete old file: %w", err)
|
||||
}
|
||||
@@ -235,7 +235,9 @@ func deleteOldFiles(dir, prefix string, removeBefore time.Time) {
|
||||
}
|
||||
if info.ModTime().Before(removeBefore) {
|
||||
if strings.HasPrefix(filepath.Base(path), prefix) {
|
||||
return util.Remove(path)
|
||||
if err = util.RemoveWithRetry(path); err != nil && !os.IsNotExist(err) {
|
||||
errorf("deleteOldFiles: failed to delete old file %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -5,6 +5,8 @@ package util
|
||||
|
||||
import "runtime"
|
||||
|
||||
const isOSWindows = runtime.GOOS == "windows"
|
||||
|
||||
func CallerFuncName(optSkipParent ...int) string {
|
||||
pc := make([]uintptr, 1)
|
||||
skipParent := 0
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
"gitea.dev/modules/log"
|
||||
repo_module "gitea.dev/modules/repository"
|
||||
"gitea.dev/modules/setting"
|
||||
@@ -342,7 +343,7 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string
|
||||
return
|
||||
}
|
||||
|
||||
fs := git.GetRepoFS(h.getStorageRepo())
|
||||
fs := gitrepo.RepoLocalFS(h.getStorageRepo())
|
||||
ctx.Resp.Header().Set("Content-Type", contentType)
|
||||
http.ServeFileFS(ctx.Resp, ctx.Req, fs, path.Clean(file))
|
||||
}
|
||||
|
||||
@@ -742,6 +742,7 @@ func MoveIssues(ctx *context.Context) {
|
||||
form := &movedIssuesForm{}
|
||||
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
|
||||
ctx.ServerError("DecodeMovedIssuesForm", err)
|
||||
return
|
||||
}
|
||||
|
||||
issueIDs := make([]int64, 0, len(form.Issues))
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"gitea.dev/models/organization"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/setting"
|
||||
@@ -251,7 +252,7 @@ func Repos(ctx *context.Context) {
|
||||
repoNames := make([]string, 0, setting.UI.Admin.UserPagingNum)
|
||||
repos := map[string]*repo_model.Repository{}
|
||||
// We're going to iterate by pagesize.
|
||||
root := user_model.UserPath(ctxUser.Name)
|
||||
root := gitrepo.UserLocalPath(ctxUser.Name)
|
||||
if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
||||
@@ -50,8 +50,8 @@ func rewriteAllPublicKeys(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
t.Close()
|
||||
if err := util.Remove(tmpPath); err != nil {
|
||||
_ = t.Close()
|
||||
if err := util.RemoveWithRetry(tmpPath); err != nil {
|
||||
log.Warn("Unable to remove temporary authorized keys file: %s: Error: %v", tmpPath, err)
|
||||
}
|
||||
}()
|
||||
@@ -74,6 +74,6 @@ func rewriteAllPublicKeys(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Close()
|
||||
return util.Rename(tmpPath, fPath)
|
||||
_ = t.Close()
|
||||
return util.RenameWithRetry(tmpPath, fPath)
|
||||
}
|
||||
|
||||
@@ -61,8 +61,8 @@ func rewriteAllPrincipalKeys(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
t.Close()
|
||||
os.Remove(tmpPath)
|
||||
_ = t.Close()
|
||||
_ = util.RemoveWithRetry(tmpPath)
|
||||
}()
|
||||
|
||||
if setting.SSH.AuthorizedPrincipalsBackup {
|
||||
@@ -83,8 +83,8 @@ func rewriteAllPrincipalKeys(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Close()
|
||||
return util.Rename(tmpPath, fPath)
|
||||
_ = t.Close()
|
||||
return util.RenameWithRetry(tmpPath, fPath)
|
||||
}
|
||||
|
||||
func regeneratePrincipalKeys(ctx context.Context, t io.Writer) error {
|
||||
|
||||
@@ -1186,9 +1186,9 @@ revert
|
||||
from :2
|
||||
D test2.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").WithRepo(pull.BaseRepo).WithStdinBytes([]byte(stdin)).Run(t.Context()))
|
||||
|
||||
gitRepo, err := git.OpenRepositoryLocal(pull.BaseRepo.RepoPath())
|
||||
gitRepo, err := git.OpenRepository(pull.BaseRepo)
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
|
||||
+3
-2
@@ -16,6 +16,7 @@ import (
|
||||
repo_model "gitea.dev/models/repo"
|
||||
secret_model "gitea.dev/models/secret"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
issue_indexer "gitea.dev/modules/indexer/issues"
|
||||
"gitea.dev/modules/storage"
|
||||
"gitea.dev/modules/structs"
|
||||
@@ -87,9 +88,9 @@ func DeleteOrganization(ctx context.Context, org *org_model.Organization, purge
|
||||
// FIXME: system notice
|
||||
// Note: There are something just cannot be roll back,
|
||||
// so just keep error logs of those operations.
|
||||
path := user_model.UserPath(org.Name)
|
||||
path := gitrepo.UserLocalPath(org.Name)
|
||||
|
||||
if err := util.RemoveAll(path); err != nil {
|
||||
if err := util.RemoveAllWithRetry(path); err != nil {
|
||||
return fmt.Errorf("failed to RemoveAll %s: %w", path, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -51,7 +52,7 @@ func TestPullRequestMergeable(t *testing.T) {
|
||||
})
|
||||
|
||||
pr.BaseBranch, pr.HeadBranch = "test-merge-tree-conflict-base", "test-merge-tree-conflict-head"
|
||||
conflictFiles := createConflictBranches(t, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.HeadBranch)
|
||||
conflictFiles := createConflictBranches(t, pr.BaseRepo, pr.BaseBranch, pr.HeadBranch)
|
||||
t.Run("Conflict-MergeTree", func(t *testing.T) {
|
||||
testPullRequestMergeCheck(t, checkPullRequestMergeableByMergeTree, pr, issues_model.PullRequestStatusConflict, conflictFiles, nil)
|
||||
})
|
||||
@@ -60,7 +61,7 @@ func TestPullRequestMergeable(t *testing.T) {
|
||||
})
|
||||
|
||||
pr.BaseBranch, pr.HeadBranch = "test-merge-tree-empty-base", "test-merge-tree-empty-head"
|
||||
createEmptyBranches(t, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.HeadBranch)
|
||||
createEmptyBranches(t, pr.BaseRepo, pr.BaseBranch, pr.HeadBranch)
|
||||
t.Run("Empty-MergeTree", func(t *testing.T) {
|
||||
testPullRequestMergeCheck(t, checkPullRequestMergeableByMergeTree, pr, issues_model.PullRequestStatusEmpty, nil, nil)
|
||||
})
|
||||
@@ -69,7 +70,7 @@ func TestPullRequestMergeable(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func createConflictBranches(t *testing.T, repoPath, baseBranch, headBranch string) []string {
|
||||
func createConflictBranches(t *testing.T, repo gitrepo.RepositoryFacade, baseBranch, headBranch string) []string {
|
||||
conflictFile := "conflict.txt"
|
||||
stdin := fmt.Sprintf(
|
||||
`reset refs/heads/%[1]s
|
||||
@@ -107,12 +108,12 @@ M 100644 inline %[3]s
|
||||
data 11
|
||||
head change
|
||||
`, baseBranch, headBranch, conflictFile)
|
||||
err := gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(stdin)).RunWithStderr(t.Context())
|
||||
err := gitcmd.NewCommand("fast-import").WithRepo(repo).WithStdinBytes([]byte(stdin)).RunWithStderr(t.Context())
|
||||
require.NoError(t, err)
|
||||
return []string{conflictFile}
|
||||
}
|
||||
|
||||
func createEmptyBranches(t *testing.T, repoPath, baseBranch, headBranch string) {
|
||||
func createEmptyBranches(t *testing.T, repo gitrepo.RepositoryFacade, baseBranch, headBranch string) {
|
||||
emptyFile := "empty.txt"
|
||||
stdin := fmt.Sprintf(`reset refs/heads/%[1]s
|
||||
from refs/heads/master
|
||||
@@ -149,6 +150,6 @@ M 100644 inline %[3]s
|
||||
data 4
|
||||
base
|
||||
`, baseBranch, headBranch, emptyFile)
|
||||
err := gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(stdin)).RunWithStderr(t.Context())
|
||||
err := gitcmd.NewCommand("fast-import").WithRepo(repo).WithStdinBytes([]byte(stdin)).RunWithStderr(t.Context())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, t
|
||||
}
|
||||
root = strings.TrimSpace(root)
|
||||
defer func() {
|
||||
_ = util.Remove(filepath.Join(tmpBasePath, root))
|
||||
_ = util.RemoveWithRetry(filepath.Join(tmpBasePath, root))
|
||||
}()
|
||||
|
||||
base, _, err := gitcmd.NewCommand("unpack-file").AddDynamicArguments(file.stage2.sha).WithRepo(tmpGitRepo).RunStdString(ctx)
|
||||
@@ -197,7 +197,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, t
|
||||
}
|
||||
base = strings.TrimSpace(filepath.Join(tmpBasePath, base))
|
||||
defer func() {
|
||||
_ = util.Remove(base)
|
||||
_ = util.RemoveWithRetry(base)
|
||||
}()
|
||||
head, _, err := gitcmd.NewCommand("unpack-file").AddDynamicArguments(file.stage3.sha).WithRepo(tmpGitRepo).RunStdString(ctx)
|
||||
if err != nil {
|
||||
@@ -205,7 +205,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, t
|
||||
}
|
||||
head = strings.TrimSpace(head)
|
||||
defer func() {
|
||||
_ = util.Remove(filepath.Join(tmpBasePath, head))
|
||||
_ = util.RemoveWithRetry(filepath.Join(tmpBasePath, head))
|
||||
}()
|
||||
|
||||
// now git merge-file annoyingly takes a different order to the merge-tree ...
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
repo_module "gitea.dev/modules/repository"
|
||||
)
|
||||
|
||||
@@ -80,8 +81,8 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest)
|
||||
outbuf: &bytes.Buffer{},
|
||||
}
|
||||
|
||||
baseRepoPath := pr.BaseRepo.RepoPath()
|
||||
headRepoPath := pr.HeadRepo.RepoPath()
|
||||
baseRepoPath := gitrepo.RepoLocalPath(pr.BaseRepo.CodeStorageRepo())
|
||||
headRepoPath := gitrepo.RepoLocalPath(pr.HeadRepo.CodeStorageRepo())
|
||||
|
||||
if err := git.InitRepositoryLocal(ctx, tmpBasePath, false, pr.BaseRepo.ObjectFormatName); err != nil {
|
||||
return nil, nil, fmt.Errorf("InitRepository[PR:%d]: %w", pr.ID, err)
|
||||
|
||||
@@ -13,8 +13,9 @@ import (
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -91,35 +92,39 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) {
|
||||
func TestAdoptRepository(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
testRepoName := "test-adopt"
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
destDir := filepath.Join(setting.RepoRootPath, user2.Name, testRepoName+".git")
|
||||
|
||||
// a successful adopt
|
||||
destDir := filepath.Join(setting.RepoRootPath, user2.Name, "test-adopt.git")
|
||||
assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.RepoRootPath, user2.Name, "repo1.git"), destDir))
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
// a successful adopt
|
||||
assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.RepoRootPath, user2.Name, "repo1.git"), destDir))
|
||||
|
||||
adoptedRepo, err := AdoptRepository(t.Context(), user2, user2, CreateRepoOptions{Name: "test-adopt"})
|
||||
assert.NoError(t, err)
|
||||
repoTestAdopt := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "test-adopt"})
|
||||
assert.Equal(t, "sha1", repoTestAdopt.ObjectFormatName)
|
||||
adoptedRepo, err := AdoptRepository(t.Context(), user2, user2, CreateRepoOptions{Name: testRepoName})
|
||||
assert.NoError(t, err)
|
||||
repoTestAdopt := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: testRepoName})
|
||||
assert.Equal(t, "sha1", repoTestAdopt.ObjectFormatName)
|
||||
|
||||
// just delete the adopted repo's db records
|
||||
err = deleteFailedAdoptRepository(adoptedRepo.ID)
|
||||
assert.NoError(t, err)
|
||||
// just delete the adopted repo's db records
|
||||
err = deleteFailedAdoptRepository(adoptedRepo.ID)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test-adopt"})
|
||||
t.Run("Failure", func(t *testing.T) {
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: testRepoName})
|
||||
// a failed adopt because some mock data
|
||||
// remove the hooks directory and create a file so that we cannot create the hooks successfully
|
||||
_ = os.RemoveAll(filepath.Join(destDir, "hooks", "update.d"))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(destDir, "hooks", "update.d"), []byte("dummy-content"), os.ModePerm))
|
||||
|
||||
// a failed adopt because some mock data
|
||||
// remove the hooks directory and create a file so that we cannot create the hooks successfully
|
||||
_ = os.RemoveAll(filepath.Join(destDir, "hooks", "update.d"))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(destDir, "hooks", "update.d"), []byte("tests"), os.ModePerm))
|
||||
adoptedRepo, err := AdoptRepository(t.Context(), user2, user2, CreateRepoOptions{Name: testRepoName})
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, adoptedRepo)
|
||||
|
||||
adoptedRepo, err = AdoptRepository(t.Context(), user2, user2, CreateRepoOptions{Name: "test-adopt"})
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, adoptedRepo)
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: testRepoName})
|
||||
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test-adopt"})
|
||||
|
||||
exist, err := util.IsExist(repo_model.RepoPath(user2.Name, "test-adopt"))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist) // the repository should be still in the disk
|
||||
exist, err := git.IsRepositoryExist(t.Context(), gitrepo.CodeRepoByName(user2.Name, testRepoName))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist) // the repository should be still in the disk
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -20,36 +21,43 @@ func TestCreateRepositoryDirectly(t *testing.T) {
|
||||
|
||||
// a successful creating repository
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
testRepoName := "created-repo"
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
createdRepo, err := CreateRepositoryDirectly(t.Context(), user2, user2, CreateRepoOptions{
|
||||
Name: testRepoName,
|
||||
}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, createdRepo)
|
||||
|
||||
createdRepo, err := CreateRepositoryDirectly(t.Context(), user2, user2, CreateRepoOptions{
|
||||
Name: "created-repo",
|
||||
}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, createdRepo)
|
||||
exist, err := git.IsRepositoryExist(t.Context(), gitrepo.CodeRepoByName(user2.Name, createdRepo.Name))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
|
||||
exist, err := util.IsExist(repo_model.RepoPath(user2.Name, createdRepo.Name))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name})
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name})
|
||||
err = DeleteRepositoryDirectly(t.Context(), createdRepo.ID)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
err = DeleteRepositoryDirectly(t.Context(), createdRepo.ID)
|
||||
assert.NoError(t, err)
|
||||
t.Run("Failure", func(t *testing.T) {
|
||||
// a failed creating because some mock data
|
||||
// create the repository directory so that the creation will fail after database record created.
|
||||
testFailureRepoName := testRepoName
|
||||
testFailureRepo := gitrepo.CodeRepoByName(user2.Name, testFailureRepoName)
|
||||
testFailurePath := gitrepo.RepoLocalPath(testFailureRepo)
|
||||
assert.NoError(t, os.MkdirAll(testFailurePath, os.ModePerm))
|
||||
|
||||
// a failed creating because some mock data
|
||||
// create the repository directory so that the creation will fail after database record created.
|
||||
assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, createdRepo.Name), os.ModePerm))
|
||||
createdRepo2, err := CreateRepositoryDirectly(t.Context(), user2, user2, CreateRepoOptions{
|
||||
Name: testFailureRepoName,
|
||||
}, true)
|
||||
assert.Nil(t, createdRepo2)
|
||||
assert.Error(t, err)
|
||||
|
||||
createdRepo2, err := CreateRepositoryDirectly(t.Context(), user2, user2, CreateRepoOptions{
|
||||
Name: "created-repo",
|
||||
}, true)
|
||||
assert.Nil(t, createdRepo2)
|
||||
assert.Error(t, err)
|
||||
// assert the cleanup is successful
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: testFailureRepoName})
|
||||
|
||||
// assert the cleanup is successful
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name})
|
||||
|
||||
exist, err = util.IsExist(repo_model.RepoPath(user2.Name, createdRepo.Name))
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
exist, err := git.IsRepositoryExist(t.Context(), testFailureRepo)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@ import (
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -52,39 +53,46 @@ func TestForkRepository(t *testing.T) {
|
||||
func TestForkRepositoryCleanup(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// a successful fork
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
|
||||
|
||||
fork, err := ForkRepository(t.Context(), user2, user2, ForkRepoOptions{
|
||||
BaseRepo: repo10,
|
||||
Name: "test",
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
// a successful fork
|
||||
|
||||
fork, err := ForkRepository(t.Context(), user2, user2, ForkRepoOptions{
|
||||
BaseRepo: repo10,
|
||||
Name: "test",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, fork)
|
||||
|
||||
exist, err := git.IsRepositoryExist(t.Context(), gitrepo.CodeRepoByName(user2.Name, "test"))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
|
||||
err = DeleteRepositoryDirectly(t.Context(), fork.ID)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, fork)
|
||||
t.Run("Failure", func(t *testing.T) {
|
||||
// a failed creating because some mock data
|
||||
// create the repository directory so that the creation will fail after database record created.
|
||||
testFailureRepoName := "test"
|
||||
testFailureRepo := gitrepo.CodeRepoByName(user2.Name, testFailureRepoName)
|
||||
testFailurePath := gitrepo.RepoLocalPath(testFailureRepo)
|
||||
assert.NoError(t, os.MkdirAll(testFailurePath, os.ModePerm))
|
||||
|
||||
exist, err := util.IsExist(repo_model.RepoPath(user2.Name, "test"))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
forkFailure, err := ForkRepository(t.Context(), user2, user2, ForkRepoOptions{
|
||||
BaseRepo: repo10,
|
||||
Name: testFailureRepoName,
|
||||
})
|
||||
assert.Nil(t, forkFailure)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = DeleteRepositoryDirectly(t.Context(), fork.ID)
|
||||
assert.NoError(t, err)
|
||||
// assert the cleanup is successful
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: testFailureRepoName})
|
||||
|
||||
// a failed creating because some mock data
|
||||
// create the repository directory so that the creation will fail after database record created.
|
||||
assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "test"), os.ModePerm))
|
||||
|
||||
fork2, err := ForkRepository(t.Context(), user2, user2, ForkRepoOptions{
|
||||
BaseRepo: repo10,
|
||||
Name: "test",
|
||||
exist, err := git.IsRepositoryExist(t.Context(), testFailureRepo)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
})
|
||||
assert.Nil(t, fork2)
|
||||
assert.Error(t, err)
|
||||
|
||||
// assert the cleanup is successful
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test"})
|
||||
|
||||
exist, err = util.IsExist(repo_model.RepoPath(user2.Name, "test"))
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = util.RemoveAll(util.FilePathJoinAbs(tmpDir, ".git")); err != nil {
|
||||
if err = util.RemoveAllWithRetry(util.FilePathJoinAbs(tmpDir, ".git")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return skippedFiles, nil
|
||||
@@ -256,7 +256,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
|
||||
return fmt.Errorf("GetTemplateSubmoduleCommits: %w", err)
|
||||
}
|
||||
|
||||
if err = util.RemoveAll(filepath.Join(tmpDir, ".git")); err != nil {
|
||||
if err = util.RemoveAllWithRetry(filepath.Join(tmpDir, ".git")); err != nil {
|
||||
return fmt.Errorf("remove git dir: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,14 @@ import (
|
||||
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/git"
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/models/organization"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
git2 "gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
"gitea.dev/modules/graceful"
|
||||
issue_indexer "gitea.dev/modules/indexer/issues"
|
||||
@@ -33,9 +33,9 @@ import (
|
||||
|
||||
// WebSearchRepository represents a repository returned by web search
|
||||
type WebSearchRepository struct {
|
||||
Repository *structs.Repository `json:"repository"`
|
||||
LatestCommitStatus *git.CommitStatus `json:"latest_commit_status"`
|
||||
LocaleLatestCommitStatus string `json:"locale_latest_commit_status"`
|
||||
Repository *structs.Repository `json:"repository"`
|
||||
LatestCommitStatus *git_model.CommitStatus `json:"latest_commit_status"`
|
||||
LocaleLatestCommitStatus string `json:"locale_latest_commit_status"`
|
||||
}
|
||||
|
||||
// WebSearchResults results of a successful web search
|
||||
@@ -218,7 +218,7 @@ func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error
|
||||
|
||||
// Create/Remove git-daemon-export-ok for git-daemon...
|
||||
daemonExportFile := `git-daemon-export-ok`
|
||||
isExist, err := git2.IsRepoFileExist(ctx, repo, daemonExportFile)
|
||||
isExist, err := git.IsRepoFileExist(ctx, repo, daemonExportFile)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
|
||||
return err
|
||||
@@ -226,11 +226,11 @@ func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error
|
||||
|
||||
isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic
|
||||
if !isPublic && isExist {
|
||||
if err = git2.RemoveRepoFileOrDir(ctx, repo, daemonExportFile); err != nil {
|
||||
if err = git.RemoveRepoFileOrDir(ctx, repo, daemonExportFile); err != nil {
|
||||
log.Error("Failed to remove %s: %v", daemonExportFile, err)
|
||||
}
|
||||
} else if isPublic && !isExist {
|
||||
if f, err := git2.CreateRepoFile(ctx, repo, daemonExportFile); err != nil {
|
||||
if f, err := git.CreateRepoFile(ctx, repo, daemonExportFile); err != nil {
|
||||
log.Error("Failed to create %s: %v", daemonExportFile, err)
|
||||
} else {
|
||||
f.Close()
|
||||
@@ -309,7 +309,7 @@ func updateRepository(ctx context.Context, repo *repo_model.Repository, visibili
|
||||
}
|
||||
|
||||
func HasWiki(ctx context.Context, repo *repo_model.Repository) bool {
|
||||
hasWiki, err := git2.IsRepositoryExist(ctx, repo.WikiStorageRepo())
|
||||
hasWiki, err := git.IsRepositoryExist(ctx, repo.WikiStorageRepo())
|
||||
if err != nil {
|
||||
log.Error("gitrepo.IsRepositoryExist: %v", err)
|
||||
}
|
||||
@@ -333,7 +333,7 @@ func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, na
|
||||
return repo_model.ErrRepoAlreadyExist{Uname: owner.Name, Name: name}
|
||||
}
|
||||
repo := gitrepo.CodeRepoByName(owner.Name, name)
|
||||
isExist, err := git2.IsRepositoryExist(ctx, repo)
|
||||
isExist, err := git.IsRepositoryExist(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if repo %s/%s exists, error: %v", owner.Name, name, err)
|
||||
return err
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/feed"
|
||||
notify_service "gitea.dev/services/notify"
|
||||
|
||||
@@ -37,20 +37,21 @@ func TestTransferOwnership(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||
assert.NoError(t, repo.LoadOwner(t.Context()))
|
||||
sourceRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||
sourceRepoBak := new(*sourceRepo)
|
||||
assert.NoError(t, sourceRepo.LoadOwner(t.Context()))
|
||||
repoTransfer := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoTransfer{ID: 1})
|
||||
assert.NoError(t, repoTransfer.LoadAttributes(t.Context()))
|
||||
assert.NoError(t, AcceptTransferOwnership(t.Context(), repo, doer))
|
||||
assert.NoError(t, AcceptTransferOwnership(t.Context(), sourceRepo, doer))
|
||||
|
||||
transferredRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||
assert.EqualValues(t, 1, transferredRepo.OwnerID) // repo_transfer.yml id=1
|
||||
unittest.AssertNotExistsBean(t, &repo_model.RepoTransfer{ID: 1})
|
||||
|
||||
exist, err := util.IsExist(repo_model.RepoPath("org3", "repo3"))
|
||||
exist, err := git.IsRepositoryExist(t.Context(), sourceRepoBak)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
exist, err = util.IsExist(repo_model.RepoPath("user1", "repo3"))
|
||||
exist, err = git.IsRepositoryExist(t.Context(), transferredRepo)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
system_model "gitea.dev/models/system"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/eventsource"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/storage"
|
||||
@@ -98,7 +99,7 @@ func RenameUser(ctx context.Context, u *user_model.User, newUserName string, doe
|
||||
}
|
||||
|
||||
// Do not fail if directory does not exist
|
||||
if err = util.Rename(user_model.UserPath(oldUserName), user_model.UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
|
||||
if err = util.RenameWithRetry(gitrepo.UserLocalPath(oldUserName), gitrepo.UserLocalPath(newUserName)); err != nil && !os.IsNotExist(err) {
|
||||
u.Name = oldUserName
|
||||
u.LowerName = strings.ToLower(oldUserName)
|
||||
return fmt.Errorf("rename user directory: %w", err)
|
||||
@@ -107,7 +108,7 @@ func RenameUser(ctx context.Context, u *user_model.User, newUserName string, doe
|
||||
if err = committer.Commit(); err != nil {
|
||||
u.Name = oldUserName
|
||||
u.LowerName = strings.ToLower(oldUserName)
|
||||
if err2 := util.Rename(user_model.UserPath(newUserName), user_model.UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
|
||||
if err2 := util.RenameWithRetry(gitrepo.UserLocalPath(newUserName), gitrepo.UserLocalPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
|
||||
log.Error("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2)
|
||||
return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2)
|
||||
}
|
||||
@@ -257,8 +258,8 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
|
||||
}
|
||||
|
||||
// Note: There are something just cannot be roll back, so just keep error logs of those operations.
|
||||
path := user_model.UserPath(u.Name)
|
||||
if err := util.RemoveAll(path); err != nil {
|
||||
path := gitrepo.UserLocalPath(u.Name)
|
||||
if err := util.RemoveAllWithRetry(path); err != nil {
|
||||
err = fmt.Errorf("failed to RemoveAll %s: %w", path, err)
|
||||
_ = system_model.CreateNotice(ctx, system_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
|
||||
}
|
||||
|
||||
@@ -211,8 +211,7 @@ func TestCompareBranchesNoCommonMergeBase(t *testing.T) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "repo1"})
|
||||
|
||||
repoPath := repo_model.RepoPath(user2.Name, repo1.Name)
|
||||
_, _, runErr := gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(strings.TrimSpace(`
|
||||
_, _, runErr := gitcmd.NewCommand("fast-import").WithRepo(repo1).WithStdinBytes([]byte(strings.TrimSpace(`
|
||||
commit refs/heads/unrelated-history
|
||||
committer User <user@example.com> 1714310400 +0000
|
||||
data 13
|
||||
|
||||
@@ -66,10 +66,10 @@ func testMain(m *testing.M) int {
|
||||
// Instead, "No tests were found", last nonsense log is "According to the configuration, subsequent logs will not be printed to the console"
|
||||
exitCode := m.Run()
|
||||
|
||||
if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil {
|
||||
if err = os.RemoveAll(setting.Indexer.IssuePath); err != nil {
|
||||
log.Error("Failed to remove indexer path: %v", err)
|
||||
}
|
||||
if err = util.RemoveAll(setting.Indexer.RepoPath); err != nil {
|
||||
if err = os.RemoveAll(setting.Indexer.RepoPath); err != nil {
|
||||
log.Error("Failed to remove indexer path: %v", err)
|
||||
}
|
||||
return exitCode
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
"gitea.dev/modules/migration"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
@@ -35,7 +36,7 @@ func TestMirrorPull(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
repoPath := repo_model.RepoPath(user.Name, repo.Name)
|
||||
repoPath := gitrepo.RepoLocalPath(repo)
|
||||
|
||||
opts := migration.MigrateOptions{
|
||||
RepoName: "test_mirror",
|
||||
@@ -140,7 +141,7 @@ func TestMirrorPullSSRFRevalidation(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
repoPath := repo_model.RepoPath(user.Name, repo.Name)
|
||||
repoPath := gitrepo.RepoLocalPath(repo)
|
||||
|
||||
// an "internal" server that records whether it was reached
|
||||
var reached atomic.Bool
|
||||
|
||||
@@ -385,13 +385,12 @@ func TestCantMergeUnrelated(t *testing.T) {
|
||||
OwnerID: user1.ID,
|
||||
Name: "repo1",
|
||||
})
|
||||
path := repo_model.RepoPath(user1.Name, repo1.Name)
|
||||
|
||||
err := gitcmd.NewCommand("read-tree", "--empty").WithDir(path).Run(t.Context())
|
||||
err := gitcmd.NewCommand("read-tree", "--empty").WithRepo(repo1).Run(t.Context())
|
||||
assert.NoError(t, err)
|
||||
|
||||
stdout, _, err := gitcmd.NewCommand("hash-object", "-w", "--stdin").
|
||||
WithDir(path).
|
||||
WithRepo(repo1).
|
||||
WithStdinBytes([]byte("Unrelated File")).
|
||||
RunStdString(t.Context())
|
||||
|
||||
@@ -400,11 +399,11 @@ func TestCantMergeUnrelated(t *testing.T) {
|
||||
|
||||
_, _, err = gitcmd.NewCommand("update-index", "--add", "--replace", "--cacheinfo").
|
||||
AddDynamicArguments("100644", sha, "somewhere-over-the-rainbow").
|
||||
WithDir(path).
|
||||
WithRepo(repo1).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
|
||||
treeSha, _, err := gitcmd.NewCommand("write-tree").WithDir(path).RunStdString(t.Context())
|
||||
treeSha, _, err := gitcmd.NewCommand("write-tree").WithRepo(repo1).RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
treeSha = strings.TrimSpace(treeSha)
|
||||
|
||||
@@ -425,7 +424,7 @@ func TestCantMergeUnrelated(t *testing.T) {
|
||||
|
||||
stdout, _, err = gitcmd.NewCommand("commit-tree").AddDynamicArguments(treeSha).
|
||||
WithEnv(env).
|
||||
WithDir(path).
|
||||
WithRepo(repo1).
|
||||
WithStdinBytes(messageBytes.Bytes()).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
@@ -433,7 +432,7 @@ func TestCantMergeUnrelated(t *testing.T) {
|
||||
|
||||
_, _, err = gitcmd.NewCommand("branch", "unrelated").
|
||||
AddDynamicArguments(commitSha).
|
||||
WithDir(path).
|
||||
WithRepo(repo1).
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
"gitea.dev/modules/util"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
"gitea.dev/tests"
|
||||
|
||||
@@ -618,57 +618,64 @@ func TestGenerateRepository(t *testing.T) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo44 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44})
|
||||
|
||||
tmplRepoLabels := []*issues_model.Label{
|
||||
{RepoID: 44, Name: "priority/high", Exclusive: true, ExclusiveOrder: 2, Color: "#ee0000", Description: "desc-high"},
|
||||
{RepoID: 44, Name: "priority/low", Exclusive: true, ExclusiveOrder: 1, Color: "#0000ee", Description: "desc-low"},
|
||||
}
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
tmplRepoLabels := []*issues_model.Label{
|
||||
{RepoID: 44, Name: "priority/high", Exclusive: true, ExclusiveOrder: 2, Color: "#ee0000", Description: "desc-high"},
|
||||
{RepoID: 44, Name: "priority/low", Exclusive: true, ExclusiveOrder: 1, Color: "#0000ee", Description: "desc-low"},
|
||||
}
|
||||
|
||||
require.NoError(t, issues_model.NewLabels(t.Context(), tmplRepoLabels...))
|
||||
require.NoError(t, issues_model.NewLabels(t.Context(), tmplRepoLabels...))
|
||||
|
||||
generatedRepo, err := repo_service.GenerateRepository(t.Context(), user2, user2, repo44, repo_service.GenerateRepoOptions{
|
||||
Name: "generated-from-template-44",
|
||||
GitContent: true,
|
||||
IssueLabels: true,
|
||||
generatedRepo, err := repo_service.GenerateRepository(t.Context(), user2, user2, repo44, repo_service.GenerateRepoOptions{
|
||||
Name: "generated-from-template-44",
|
||||
GitContent: true,
|
||||
IssueLabels: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, generatedRepo)
|
||||
|
||||
exist, err := git.IsRepositoryExist(t.Context(), generatedRepo)
|
||||
require.NoError(t, err)
|
||||
require.True(t, exist)
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name})
|
||||
|
||||
generatedLabels, err := issues_model.GetLabelsByRepoID(t.Context(), generatedRepo.ID, "", db.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, generatedLabels, len(tmplRepoLabels))
|
||||
for i, tmplLabel := range tmplRepoLabels {
|
||||
genLabel := generatedLabels[i]
|
||||
assert.Equal(t, tmplLabel.Name, genLabel.Name)
|
||||
assert.Equal(t, tmplLabel.Exclusive, genLabel.Exclusive)
|
||||
assert.Equal(t, tmplLabel.ExclusiveOrder, genLabel.ExclusiveOrder)
|
||||
assert.Equal(t, tmplLabel.Color, genLabel.Color)
|
||||
assert.Equal(t, tmplLabel.Description, genLabel.Description)
|
||||
}
|
||||
|
||||
err = repo_service.DeleteRepositoryDirectly(t.Context(), generatedRepo.ID)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, generatedRepo)
|
||||
|
||||
exist, err := util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name))
|
||||
require.NoError(t, err)
|
||||
require.True(t, exist)
|
||||
t.Run("Failure", func(t *testing.T) {
|
||||
// a failed creating because some mock data
|
||||
// create the repository directory so that the creation will fail after database record created.
|
||||
testFailureRepoName := "generated-from-template-44"
|
||||
testFailureRepo := gitrepo.CodeRepoByName(user2.Name, testFailureRepoName)
|
||||
testFailurePath := gitrepo.RepoLocalPath(testFailureRepo)
|
||||
assert.NoError(t, os.MkdirAll(testFailurePath, os.ModePerm))
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name})
|
||||
generatedRepoFailure, err := repo_service.GenerateRepository(t.Context(), user2, user2, repo44, repo_service.GenerateRepoOptions{
|
||||
Name: testFailureRepoName,
|
||||
GitContent: true,
|
||||
})
|
||||
assert.Nil(t, generatedRepoFailure)
|
||||
assert.Error(t, err)
|
||||
|
||||
generatedLabels, err := issues_model.GetLabelsByRepoID(t.Context(), generatedRepo.ID, "", db.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, generatedLabels, len(tmplRepoLabels))
|
||||
for i, tmplLabel := range tmplRepoLabels {
|
||||
genLabel := generatedLabels[i]
|
||||
assert.Equal(t, tmplLabel.Name, genLabel.Name)
|
||||
assert.Equal(t, tmplLabel.Exclusive, genLabel.Exclusive)
|
||||
assert.Equal(t, tmplLabel.ExclusiveOrder, genLabel.ExclusiveOrder)
|
||||
assert.Equal(t, tmplLabel.Color, genLabel.Color)
|
||||
assert.Equal(t, tmplLabel.Description, genLabel.Description)
|
||||
}
|
||||
// assert the cleanup is successful
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: testFailureRepoName})
|
||||
|
||||
err = repo_service.DeleteRepositoryDirectly(t.Context(), generatedRepo.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// a failed creating because some mock data
|
||||
// create the repository directory so that the creation will fail after database record created.
|
||||
assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "generated-from-template-44"), os.ModePerm))
|
||||
|
||||
generatedRepo2, err := repo_service.GenerateRepository(t.Context(), user2, user2, repo44, repo_service.GenerateRepoOptions{
|
||||
Name: "generated-from-template-44",
|
||||
GitContent: true,
|
||||
exist, err := git.IsRepositoryExist(t.Context(), testFailureRepo)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
})
|
||||
assert.Nil(t, generatedRepo2)
|
||||
assert.Error(t, err)
|
||||
|
||||
// assert the cleanup is successful
|
||||
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name})
|
||||
|
||||
exist, err = util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name))
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user