Skip to content

Commit 26ef180

Browse files
authored
Correctly rollback in ForkRepository (#17034)
The rollback functionality in services/repository/repository.go:ForkRepository is incorrect and could lead to a deadlock as it uses DeleteRepository to delete the rolled-back repository - a function which creates its own transaction. This PR adjusts the rollback function to only use RemoveAll as any database changes will be automatically rolled-back. It also handles panics and adjusts the Close within WithTx to ensure that if there is a panic the session will always be closed. Signed-off-by: Andrew Thornton <art27@cantab.net>
1 parent 04b233e commit 26ef180

File tree

2 files changed

+41
-21
lines changed

2 files changed

+41
-21
lines changed

models/context.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,16 @@ func WithContext(f func(ctx DBContext) error) error {
4545
// WithTx represents executing database operations on a transaction
4646
func WithTx(f func(ctx DBContext) error) error {
4747
sess := x.NewSession()
48+
defer sess.Close()
4849
if err := sess.Begin(); err != nil {
49-
sess.Close()
5050
return err
5151
}
5252

5353
if err := f(DBContext{sess}); err != nil {
54-
sess.Close()
5554
return err
5655
}
5756

58-
err := sess.Commit()
59-
sess.Close()
60-
return err
57+
return sess.Commit()
6158
}
6259

6360
// Iterate iterates the databases and doing something

modules/repository/fork.go

+39-16
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"code.gitea.io/gitea/modules/git"
1414
"code.gitea.io/gitea/modules/log"
1515
"code.gitea.io/gitea/modules/structs"
16+
"code.gitea.io/gitea/modules/util"
1617
)
1718

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

4647
oldRepoPath := opts.BaseRepo.RepoPath()
4748

49+
needsRollback := false
50+
rollbackFn := func() {
51+
if !needsRollback {
52+
return
53+
}
54+
55+
repoPath := models.RepoPath(owner.Name, repo.Name)
56+
57+
if exists, _ := util.IsExist(repoPath); !exists {
58+
return
59+
}
60+
61+
// As the transaction will be failed and hence database changes will be destroyed we only need
62+
// to delete the related repository on the filesystem
63+
if errDelete := util.RemoveAll(repoPath); errDelete != nil {
64+
log.Error("Failed to remove fork repo")
65+
}
66+
}
67+
68+
needsRollbackInPanic := true
69+
defer func() {
70+
panicErr := recover()
71+
if panicErr == nil {
72+
return
73+
}
74+
75+
if needsRollbackInPanic {
76+
rollbackFn()
77+
}
78+
panic(panicErr)
79+
}()
80+
4881
err = models.WithTx(func(ctx models.DBContext) error {
4982
if err = models.CreateRepository(ctx, doer, owner, repo, false); err != nil {
5083
return err
5184
}
5285

53-
rollbackRemoveFn := func() {
54-
if repo.ID == 0 {
55-
return
56-
}
57-
if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil {
58-
log.Error("Rollback deleteRepository: %v", errDelete)
59-
}
60-
}
61-
6286
if err = models.IncrementRepoForkNum(ctx, opts.BaseRepo.ID); err != nil {
63-
rollbackRemoveFn()
6487
return err
6588
}
6689

6790
// copy lfs files failure should not be ignored
68-
if err := models.CopyLFS(ctx, repo, opts.BaseRepo); err != nil {
69-
rollbackRemoveFn()
91+
if err = models.CopyLFS(ctx, repo, opts.BaseRepo); err != nil {
7092
return err
7193
}
7294

95+
needsRollback = true
96+
7397
repoPath := models.RepoPath(owner.Name, repo.Name)
7498
if stdout, err := git.NewCommand(
7599
"clone", "--bare", oldRepoPath, repoPath).
76100
SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", opts.BaseRepo.FullName(), repo.FullName())).
77101
RunInDirTimeout(10*time.Minute, ""); err != nil {
78102
log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err)
79-
rollbackRemoveFn()
80103
return fmt.Errorf("git clone: %v", err)
81104
}
82105

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

91113
if err = createDelegateHooks(repoPath); err != nil {
92-
rollbackRemoveFn()
93114
return fmt.Errorf("createDelegateHooks: %v", err)
94115
}
95116
return nil
96117
})
118+
needsRollbackInPanic = false
97119
if err != nil {
120+
rollbackFn()
98121
return nil, err
99122
}
100123

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

0 commit comments

Comments
 (0)