Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly rollback in ForkRepository #17034

Merged
merged 2 commits into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions models/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,16 @@ func WithContext(f func(ctx DBContext) error) error {
// WithTx represents executing database operations on a transaction
func WithTx(f func(ctx DBContext) error) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
sess.Close()
return err
}

if err := f(DBContext{sess}); err != nil {
sess.Close()
return err
}

err := sess.Commit()
sess.Close()
return err
return sess.Commit()
}

// Iterate iterates the databases and doing something
Expand Down
55 changes: 39 additions & 16 deletions modules/repository/fork.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
)

// ForkRepository forks a repository
Expand Down Expand Up @@ -45,62 +46,84 @@ func ForkRepository(doer, owner *models.User, opts models.ForkRepoOptions) (_ *m

oldRepoPath := opts.BaseRepo.RepoPath()

needsRollback := false
rollbackFn := func() {
if !needsRollback {
return
}

repoPath := models.RepoPath(owner.Name, repo.Name)

if exists, _ := util.IsExist(repoPath); !exists {
return
}

// As the transaction will be failed and hence database changes will be destroyed we only need
// to delete the related repository on the filesystem
if errDelete := util.RemoveAll(repoPath); errDelete != nil {
log.Error("Failed to remove fork repo")
}
}

needsRollbackInPanic := true
defer func() {
panicErr := recover()
if panicErr == nil {
return
}

if needsRollbackInPanic {
rollbackFn()
}
panic(panicErr)
}()

err = models.WithTx(func(ctx models.DBContext) error {
if err = models.CreateRepository(ctx, doer, owner, repo, false); err != nil {
return err
}

rollbackRemoveFn := func() {
if repo.ID == 0 {
return
}
if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil {
log.Error("Rollback deleteRepository: %v", errDelete)
}
}

if err = models.IncrementRepoForkNum(ctx, opts.BaseRepo.ID); err != nil {
rollbackRemoveFn()
return err
}

// copy lfs files failure should not be ignored
if err := models.CopyLFS(ctx, repo, opts.BaseRepo); err != nil {
rollbackRemoveFn()
if err = models.CopyLFS(ctx, repo, opts.BaseRepo); err != nil {
return err
}

needsRollback = true

repoPath := models.RepoPath(owner.Name, repo.Name)
if stdout, err := git.NewCommand(
"clone", "--bare", oldRepoPath, repoPath).
SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", opts.BaseRepo.FullName(), repo.FullName())).
RunInDirTimeout(10*time.Minute, ""); err != nil {
log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err)
rollbackRemoveFn()
return fmt.Errorf("git clone: %v", err)
}

if stdout, err := git.NewCommand("update-server-info").
SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())).
RunInDir(repoPath); err != nil {
log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err)
rollbackRemoveFn()
return fmt.Errorf("git update-server-info: %v", err)
}

if err = createDelegateHooks(repoPath); err != nil {
rollbackRemoveFn()
return fmt.Errorf("createDelegateHooks: %v", err)
}
return nil
})
needsRollbackInPanic = false
if err != nil {
rollbackFn()
return nil, err
}

// even if below operations failed, it could be ignored. And they will be retried
ctx := models.DefaultDBContext()
if err = repo.UpdateSize(ctx); err != nil {
if err := repo.UpdateSize(ctx); err != nil {
log.Error("Failed to update size for repository: %v", err)
}
if err := models.CopyLanguageStat(opts.BaseRepo, repo); err != nil {
Expand Down