mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28: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:
@@ -5,12 +5,17 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
)
|
||||
|
||||
@@ -69,66 +74,120 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
|
||||
}
|
||||
}
|
||||
|
||||
var generateRepo *repo_model.Repository
|
||||
if err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
generateRepo, err = generateRepository(ctx, doer, owner, templateRepo, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
generateRepo := &repo_model.Repository{
|
||||
OwnerID: owner.ID,
|
||||
Owner: owner,
|
||||
OwnerName: owner.Name,
|
||||
Name: opts.Name,
|
||||
LowerName: strings.ToLower(opts.Name),
|
||||
Description: opts.Description,
|
||||
DefaultBranch: opts.DefaultBranch,
|
||||
IsPrivate: opts.Private,
|
||||
IsEmpty: !opts.GitContent || templateRepo.IsEmpty,
|
||||
IsFsckEnabled: templateRepo.IsFsckEnabled,
|
||||
TemplateID: templateRepo.ID,
|
||||
TrustModel: templateRepo.TrustModel,
|
||||
ObjectFormatName: templateRepo.ObjectFormatName,
|
||||
Status: repo_model.RepositoryBeingMigrated,
|
||||
}
|
||||
|
||||
// Git Content
|
||||
if opts.GitContent && !templateRepo.IsEmpty {
|
||||
if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Topics
|
||||
if opts.Topics {
|
||||
if err = repo_model.GenerateTopics(ctx, templateRepo, generateRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Git Hooks
|
||||
if opts.GitHooks {
|
||||
if err = GenerateGitHooks(ctx, templateRepo, generateRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Webhooks
|
||||
if opts.Webhooks {
|
||||
if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Avatar
|
||||
if opts.Avatar && len(templateRepo.Avatar) > 0 {
|
||||
if err = generateAvatar(ctx, templateRepo, generateRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Issue Labels
|
||||
if opts.IssueLabels {
|
||||
if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.ProtectedBranch {
|
||||
if err = GenerateProtectedBranch(ctx, templateRepo, generateRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
// 1 - Create the repository in the database
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
return createRepositoryInDB(ctx, doer, owner, generateRepo, false)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// last - clean up the repository if something goes wrong
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// we can not use the ctx because it maybe canceled or timeout
|
||||
cleanupRepository(doer, generateRepo.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
// 2 - check whether the repository with the same storage exists
|
||||
isExist, err := gitrepo.IsRepositoryExist(ctx, generateRepo)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", generateRepo.FullName(), err)
|
||||
return nil, err
|
||||
}
|
||||
if isExist {
|
||||
// Don't return directly, we need err in defer to cleanupRepository
|
||||
err = repo_model.ErrRepoFilesAlreadyExist{
|
||||
Uname: generateRepo.OwnerName,
|
||||
Name: generateRepo.Name,
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3 -Init git bare new repository.
|
||||
if err = git.InitRepository(ctx, generateRepo.RepoPath(), true, generateRepo.ObjectFormatName); err != nil {
|
||||
return nil, fmt.Errorf("git.InitRepository: %w", err)
|
||||
} else if err = gitrepo.CreateDelegateHooks(ctx, generateRepo); err != nil {
|
||||
return nil, fmt.Errorf("createDelegateHooks: %w", err)
|
||||
}
|
||||
|
||||
// 4 - Update the git repository
|
||||
if err = updateGitRepoAfterCreate(ctx, generateRepo); err != nil {
|
||||
return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err)
|
||||
}
|
||||
|
||||
// 5 - generate the repository contents according to the template
|
||||
// Git Content
|
||||
if opts.GitContent && !templateRepo.IsEmpty {
|
||||
if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Topics
|
||||
if opts.Topics {
|
||||
if err = repo_model.GenerateTopics(ctx, templateRepo, generateRepo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Git Hooks
|
||||
if opts.GitHooks {
|
||||
if err = GenerateGitHooks(ctx, templateRepo, generateRepo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Webhooks
|
||||
if opts.Webhooks {
|
||||
if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Avatar
|
||||
if opts.Avatar && len(templateRepo.Avatar) > 0 {
|
||||
if err = generateAvatar(ctx, templateRepo, generateRepo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Issue Labels
|
||||
if opts.IssueLabels {
|
||||
if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.ProtectedBranch {
|
||||
if err = GenerateProtectedBranch(ctx, templateRepo, generateRepo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 6 - update repository status to be ready
|
||||
generateRepo.Status = repo_model.RepositoryReady
|
||||
if err = repo_model.UpdateRepositoryCols(ctx, generateRepo, "status"); err != nil {
|
||||
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
||||
}
|
||||
|
||||
notify_service.CreateRepository(ctx, doer, owner, generateRepo)
|
||||
|
||||
return generateRepo, nil
|
||||
|
Reference in New Issue
Block a user