mirror of
https://github.com/go-gitea/gitea
synced 2025-07-19 08:48:37 +00:00
Rework create/fork/adopt/generate repository to make sure resources will be cleanup once failed (#31035)
Fix #28144 To make the resources will be cleanup once failed. All repository operations now follow a consistent pattern: - 1. Create a database record for the repository with the status being_migrated. - 2. Register a deferred cleanup function to delete the repository and its related data if the operation fails. - 3. Perform the actual Git and database operations step by step. - 4. Upon successful completion, update the repository’s status to ready. The adopt operation is a special case — if it fails, the repository on disk should not be deleted.
This commit is contained in:
@@ -16,7 +16,6 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
@@ -28,6 +27,18 @@ import (
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
func deleteFailedAdoptRepository(repoID int64) error {
|
||||
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||
if err := deleteDBRepository(ctx, repoID); err != nil {
|
||||
return fmt.Errorf("deleteDBRepository: %w", err)
|
||||
}
|
||||
if err := git_model.DeleteRepoBranches(ctx, repoID); err != nil {
|
||||
return fmt.Errorf("deleteRepoBranches: %w", err)
|
||||
}
|
||||
return repo_model.DeleteRepoReleases(ctx, repoID)
|
||||
})
|
||||
}
|
||||
|
||||
// AdoptRepository adopts pre-existing repository files for the user/organization.
|
||||
func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) {
|
||||
if !doer.IsAdmin && !u.CanCreateRepo() {
|
||||
@@ -48,58 +59,51 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR
|
||||
IsPrivate: opts.IsPrivate,
|
||||
IsFsckEnabled: !opts.IsMirror,
|
||||
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
|
||||
Status: opts.Status,
|
||||
Status: repo_model.RepositoryBeingMigrated,
|
||||
IsEmpty: !opts.AutoInit,
|
||||
}
|
||||
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
isExist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
||||
// 1 - create the repository database operations first
|
||||
err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
return createRepositoryInDB(ctx, doer, u, repo, false)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// last - clean up if something goes wrong
|
||||
// WARNING: Don't override all later err with local variables
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err)
|
||||
return err
|
||||
}
|
||||
if !isExist {
|
||||
return repo_model.ErrRepoNotExist{
|
||||
OwnerName: u.Name,
|
||||
Name: repo.Name,
|
||||
// we can not use the ctx because it maybe canceled or timeout
|
||||
if errDel := deleteFailedAdoptRepository(repo.ID); errDel != nil {
|
||||
log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := CreateRepositoryByExample(ctx, doer, u, repo, true, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Re-fetch the repository from database before updating it (else it would
|
||||
// override changes that were done earlier with sql)
|
||||
if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil {
|
||||
return fmt.Errorf("getRepositoryByID: %w", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
// Re-fetch the repository from database before updating it (else it would
|
||||
// override changes that were done earlier with sql)
|
||||
if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil {
|
||||
return nil, fmt.Errorf("getRepositoryByID: %w", err)
|
||||
}
|
||||
|
||||
if err := func() error {
|
||||
if err := adoptRepository(ctx, repo, opts.DefaultBranch); err != nil {
|
||||
return fmt.Errorf("adoptRepository: %w", err)
|
||||
}
|
||||
|
||||
if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil {
|
||||
return fmt.Errorf("checkDaemonExportOK: %w", err)
|
||||
}
|
||||
|
||||
if stdout, _, err := git.NewCommand("update-server-info").
|
||||
RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil {
|
||||
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
|
||||
return fmt.Errorf("CreateRepository(git update-server-info): %w", err)
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
if errDel := DeleteRepository(ctx, doer, repo, false /* no notify */); errDel != nil {
|
||||
log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel)
|
||||
}
|
||||
return nil, err
|
||||
// 2 - adopt the repository from disk
|
||||
if err = adoptRepository(ctx, repo, opts.DefaultBranch); err != nil {
|
||||
return nil, fmt.Errorf("adoptRepository: %w", err)
|
||||
}
|
||||
|
||||
// 3 - Update the git repository
|
||||
if err = updateGitRepoAfterCreate(ctx, repo); err != nil {
|
||||
return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err)
|
||||
}
|
||||
|
||||
// 4 - update repository status
|
||||
repo.Status = repo_model.RepositoryReady
|
||||
if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
||||
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
||||
}
|
||||
|
||||
notify_service.AdoptRepository(ctx, doer, u, repo)
|
||||
|
||||
return repo, nil
|
||||
|
Reference in New Issue
Block a user