Skip to content

Commit c6cf96d

Browse files
lunny6543wxiaoguang
authored
Fix automerge will not work because of some events haven't been triggered (#30780)
Replace #25741 Close #24445 Close #30658 Close #20646 ~Depends on #30805~ Since #25741 has been rewritten totally, to make the contribution easier, I will continue the work in this PR. Thanks @6543 --------- Co-authored-by: 6543 <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent 1007ce7 commit c6cf96d

File tree

7 files changed

+344
-63
lines changed

7 files changed

+344
-63
lines changed

models/issues/review.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,14 @@ func (r *Review) LoadCodeComments(ctx context.Context) (err error) {
155155
if r.CodeComments != nil {
156156
return err
157157
}
158-
if err = r.loadIssue(ctx); err != nil {
158+
if err = r.LoadIssue(ctx); err != nil {
159159
return err
160160
}
161161
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false)
162162
return err
163163
}
164164

165-
func (r *Review) loadIssue(ctx context.Context) (err error) {
165+
func (r *Review) LoadIssue(ctx context.Context) (err error) {
166166
if r.Issue != nil {
167167
return err
168168
}
@@ -199,7 +199,7 @@ func (r *Review) LoadReviewerTeam(ctx context.Context) (err error) {
199199

200200
// LoadAttributes loads all attributes except CodeComments
201201
func (r *Review) LoadAttributes(ctx context.Context) (err error) {
202-
if err = r.loadIssue(ctx); err != nil {
202+
if err = r.LoadIssue(ctx); err != nil {
203203
return err
204204
}
205205
if err = r.LoadCodeComments(ctx); err != nil {

services/automerge/automerge.go

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"code.gitea.io/gitea/modules/log"
2323
"code.gitea.io/gitea/modules/process"
2424
"code.gitea.io/gitea/modules/queue"
25+
notify_service "code.gitea.io/gitea/services/notify"
2526
pull_service "code.gitea.io/gitea/services/pull"
2627
)
2728

@@ -30,6 +31,8 @@ var prAutoMergeQueue *queue.WorkerPoolQueue[string]
3031

3132
// Init runs the task queue to that handles auto merges
3233
func Init() error {
34+
notify_service.RegisterNotifier(NewNotifier())
35+
3336
prAutoMergeQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_auto_merge", handler)
3437
if prAutoMergeQueue == nil {
3538
return fmt.Errorf("unable to create pr_auto_merge queue")
@@ -47,7 +50,7 @@ func handler(items ...string) []string {
4750
log.Error("could not parse data from pr_auto_merge queue (%v): %v", s, err)
4851
continue
4952
}
50-
handlePull(id, sha)
53+
handlePullRequestAutoMerge(id, sha)
5154
}
5255
return nil
5356
}
@@ -62,16 +65,6 @@ func addToQueue(pr *issues_model.PullRequest, sha string) {
6265
// ScheduleAutoMerge if schedule is false and no error, pull can be merged directly
6366
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string) (scheduled bool, err error) {
6467
err = db.WithTx(ctx, func(ctx context.Context) error {
65-
lastCommitStatus, err := pull_service.GetPullRequestCommitStatusState(ctx, pull)
66-
if err != nil {
67-
return err
68-
}
69-
70-
// we don't need to schedule
71-
if lastCommitStatus.IsSuccess() {
72-
return nil
73-
}
74-
7568
if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message); err != nil {
7669
return err
7770
}
@@ -95,8 +88,8 @@ func RemoveScheduledAutoMerge(ctx context.Context, doer *user_model.User, pull *
9588
})
9689
}
9790

98-
// MergeScheduledPullRequest merges a previously scheduled pull request when all checks succeeded
99-
func MergeScheduledPullRequest(ctx context.Context, sha string, repo *repo_model.Repository) error {
91+
// StartPRCheckAndAutoMergeBySHA start an automerge check and auto merge task for all pull requests of repository and SHA
92+
func StartPRCheckAndAutoMergeBySHA(ctx context.Context, sha string, repo *repo_model.Repository) error {
10093
pulls, err := getPullRequestsByHeadSHA(ctx, sha, repo, func(pr *issues_model.PullRequest) bool {
10194
return !pr.HasMerged && pr.CanAutoMerge()
10295
})
@@ -111,6 +104,32 @@ func MergeScheduledPullRequest(ctx context.Context, sha string, repo *repo_model
111104
return nil
112105
}
113106

107+
// StartPRCheckAndAutoMerge start an automerge check and auto merge task for a pull request
108+
func StartPRCheckAndAutoMerge(ctx context.Context, pull *issues_model.PullRequest) {
109+
if pull == nil || pull.HasMerged || !pull.CanAutoMerge() {
110+
return
111+
}
112+
113+
if err := pull.LoadBaseRepo(ctx); err != nil {
114+
log.Error("LoadBaseRepo: %v", err)
115+
return
116+
}
117+
118+
gitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo)
119+
if err != nil {
120+
log.Error("OpenRepository: %v", err)
121+
return
122+
}
123+
defer gitRepo.Close()
124+
commitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
125+
if err != nil {
126+
log.Error("GetRefCommitID: %v", err)
127+
return
128+
}
129+
130+
addToQueue(pull, commitID)
131+
}
132+
114133
func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model.Repository, filter func(*issues_model.PullRequest) bool) (map[int64]*issues_model.PullRequest, error) {
115134
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
116135
if err != nil {
@@ -161,7 +180,8 @@ func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model.
161180
return pulls, nil
162181
}
163182

164-
func handlePull(pullID int64, sha string) {
183+
// handlePullRequestAutoMerge merge the pull request if all checks are successful
184+
func handlePullRequestAutoMerge(pullID int64, sha string) {
165185
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(),
166186
fmt.Sprintf("Handle AutoMerge of PR[%d] with sha[%s]", pullID, sha))
167187
defer finished()
@@ -182,24 +202,50 @@ func handlePull(pullID int64, sha string) {
182202
return
183203
}
184204

205+
if err = pr.LoadBaseRepo(ctx); err != nil {
206+
log.Error("%-v LoadBaseRepo: %v", pr, err)
207+
return
208+
}
209+
210+
// check the sha is the same as pull request head commit id
211+
baseGitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
212+
if err != nil {
213+
log.Error("OpenRepository: %v", err)
214+
return
215+
}
216+
defer baseGitRepo.Close()
217+
218+
headCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
219+
if err != nil {
220+
log.Error("GetRefCommitID: %v", err)
221+
return
222+
}
223+
if headCommitID != sha {
224+
log.Warn("Head commit id of auto merge %-v does not match sha [%s], it may means the head branch has been updated. Just ignore this request because a new request expected in the queue", pr, sha)
225+
return
226+
}
227+
185228
// Get all checks for this pr
186229
// We get the latest sha commit hash again to handle the case where the check of a previous push
187230
// did not succeed or was not finished yet.
188-
189231
if err = pr.LoadHeadRepo(ctx); err != nil {
190232
log.Error("%-v LoadHeadRepo: %v", pr, err)
191233
return
192234
}
193235

194-
headGitRepo, err := gitrepo.OpenRepository(ctx, pr.HeadRepo)
195-
if err != nil {
196-
log.Error("OpenRepository %-v: %v", pr.HeadRepo, err)
197-
return
236+
var headGitRepo *git.Repository
237+
if pr.BaseRepoID == pr.HeadRepoID {
238+
headGitRepo = baseGitRepo
239+
} else {
240+
headGitRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
241+
if err != nil {
242+
log.Error("OpenRepository %-v: %v", pr.HeadRepo, err)
243+
return
244+
}
245+
defer headGitRepo.Close()
198246
}
199-
defer headGitRepo.Close()
200247

201248
headBranchExist := headGitRepo.IsBranchExist(pr.HeadBranch)
202-
203249
if pr.HeadRepo == nil || !headBranchExist {
204250
log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch: %s]", pr, pr.HeadRepoID, pr.HeadBranch)
205251
return
@@ -238,25 +284,11 @@ func handlePull(pullID int64, sha string) {
238284
return
239285
}
240286

241-
var baseGitRepo *git.Repository
242-
if pr.BaseRepoID == pr.HeadRepoID {
243-
baseGitRepo = headGitRepo
244-
} else {
245-
if err = pr.LoadBaseRepo(ctx); err != nil {
246-
log.Error("%-v LoadBaseRepo: %v", pr, err)
247-
return
248-
}
249-
250-
baseGitRepo, err = gitrepo.OpenRepository(ctx, pr.BaseRepo)
251-
if err != nil {
252-
log.Error("OpenRepository %-v: %v", pr.BaseRepo, err)
253-
return
254-
}
255-
defer baseGitRepo.Close()
256-
}
257-
258287
if err := pull_service.Merge(ctx, pr, doer, baseGitRepo, scheduledPRM.MergeStyle, "", scheduledPRM.Message, true); err != nil {
259288
log.Error("pull_service.Merge: %v", err)
289+
// FIXME: if merge failed, we should display some error message to the pull request page.
290+
// The resolution is add a new column on automerge table named `error_message` to store the error message and displayed
291+
// on the pull request page. But this should not be finished in a bug fix PR which will be backport to release branch.
260292
return
261293
}
262294
}

services/automerge/notify.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package automerge
5+
6+
import (
7+
"context"
8+
9+
issues_model "code.gitea.io/gitea/models/issues"
10+
user_model "code.gitea.io/gitea/models/user"
11+
"code.gitea.io/gitea/modules/log"
12+
notify_service "code.gitea.io/gitea/services/notify"
13+
)
14+
15+
type automergeNotifier struct {
16+
notify_service.NullNotifier
17+
}
18+
19+
var _ notify_service.Notifier = &automergeNotifier{}
20+
21+
// NewNotifier create a new automergeNotifier notifier
22+
func NewNotifier() notify_service.Notifier {
23+
return &automergeNotifier{}
24+
}
25+
26+
func (n *automergeNotifier) PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
27+
// as a missing / blocking reviews could have blocked a pending automerge let's recheck
28+
if review.Type == issues_model.ReviewTypeApprove {
29+
if err := StartPRCheckAndAutoMergeBySHA(ctx, review.CommitID, pr.BaseRepo); err != nil {
30+
log.Error("StartPullRequestAutoMergeCheckBySHA: %v", err)
31+
}
32+
}
33+
}
34+
35+
func (n *automergeNotifier) PullReviewDismiss(ctx context.Context, doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
36+
if err := review.LoadIssue(ctx); err != nil {
37+
log.Error("LoadIssue: %v", err)
38+
return
39+
}
40+
if err := review.Issue.LoadPullRequest(ctx); err != nil {
41+
log.Error("LoadPullRequest: %v", err)
42+
return
43+
}
44+
// as reviews could have blocked a pending automerge let's recheck
45+
StartPRCheckAndAutoMerge(ctx, review.Issue.PullRequest)
46+
}

services/repository/commitstatus/commitstatus.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
115115
}
116116

117117
if status.State.IsSuccess() {
118-
if err := automerge.MergeScheduledPullRequest(ctx, sha, repo); err != nil {
118+
if err := automerge.StartPRCheckAndAutoMergeBySHA(ctx, sha, repo); err != nil {
119119
return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
120120
}
121121
}

tests/integration/editor_test.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package integration
55

66
import (
7+
"fmt"
78
"net/http"
89
"net/http/httptest"
910
"net/url"
@@ -19,25 +20,29 @@ import (
1920
func TestCreateFile(t *testing.T) {
2021
onGiteaRun(t, func(t *testing.T, u *url.URL) {
2122
session := loginUser(t, "user2")
23+
testCreateFile(t, session, "user2", "repo1", "master", "test.txt", "Content")
24+
})
25+
}
2226

23-
// Request editor page
24-
req := NewRequest(t, "GET", "/user2/repo1/_new/master/")
25-
resp := session.MakeRequest(t, req, http.StatusOK)
27+
func testCreateFile(t *testing.T, session *TestSession, user, repo, branch, filePath, content string) *httptest.ResponseRecorder {
28+
// Request editor page
29+
newURL := fmt.Sprintf("/%s/%s/_new/%s/", user, repo, branch)
30+
req := NewRequest(t, "GET", newURL)
31+
resp := session.MakeRequest(t, req, http.StatusOK)
2632

27-
doc := NewHTMLParser(t, resp.Body)
28-
lastCommit := doc.GetInputValueByName("last_commit")
29-
assert.NotEmpty(t, lastCommit)
33+
doc := NewHTMLParser(t, resp.Body)
34+
lastCommit := doc.GetInputValueByName("last_commit")
35+
assert.NotEmpty(t, lastCommit)
3036

31-
// Save new file to master branch
32-
req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{
33-
"_csrf": doc.GetCSRF(),
34-
"last_commit": lastCommit,
35-
"tree_path": "test.txt",
36-
"content": "Content",
37-
"commit_choice": "direct",
38-
})
39-
session.MakeRequest(t, req, http.StatusSeeOther)
37+
// Save new file to master branch
38+
req = NewRequestWithValues(t, "POST", newURL, map[string]string{
39+
"_csrf": doc.GetCSRF(),
40+
"last_commit": lastCommit,
41+
"tree_path": filePath,
42+
"content": content,
43+
"commit_choice": "direct",
4044
})
45+
return session.MakeRequest(t, req, http.StatusSeeOther)
4146
}
4247

4348
func TestCreateFileOnProtectedBranch(t *testing.T) {

0 commit comments

Comments
 (0)