Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f51af18
wip yES i Know theres is a cycle
6543 Jul 7, 2023
d145c26
fix & finish
6543 Mar 2, 2024
0dc9531
Use notify to trigger auto merge check
lunny Apr 18, 2024
e4e24d2
Remove unnecessary changes
lunny Apr 18, 2024
db6e278
Fix bugs
lunny Apr 23, 2024
a5d0708
Fix bug
lunny Apr 23, 2024
b208bde
Fix lint
lunny Apr 23, 2024
c1eac83
Add transaction for auto merge
lunny Apr 24, 2024
3ec2753
Close git repo earlier
lunny Apr 30, 2024
b541467
Merge branch 'main' into lunny/fix_automerge
lunny May 7, 2024
ed361d2
Revert unnecessary change
lunny May 7, 2024
f5e4176
Revert unnecessary change
lunny May 7, 2024
531e8db
Revert unnecessary change
lunny May 7, 2024
9331ccd
Use better names for the automerge functions
lunny May 7, 2024
8ecfb7d
Merge branch 'main' into lunny/fix_automerge
lunny May 7, 2024
7d14dfc
Improve the comments
lunny May 7, 2024
51e4888
Merge branch 'main' into lunny/fix_automerge
lunny May 9, 2024
797236c
Add tests for pull request automerge when updating or commit status s…
lunny May 10, 2024
5c0f00a
Fix test
lunny May 10, 2024
44b306a
Fix test
lunny May 11, 2024
ee1aba3
Merge branch 'main' into lunny/fix_automerge
lunny May 11, 2024
0d4cce7
Fix tests
lunny May 11, 2024
438d880
Fix test
lunny May 11, 2024
fd8dda3
improvment
lunny May 13, 2024
93c9739
Merge branch 'main' into lunny/fix_automerge
lunny May 13, 2024
deeea79
add trace for test
lunny May 18, 2024
8331fa0
Add trace
lunny May 18, 2024
d9e8a42
add trace for test
lunny May 19, 2024
e447ec2
improve test
lunny May 19, 2024
8d98ed5
Merge branch 'main' into lunny/fix_automerge
lunny May 20, 2024
a0adfd5
Fix test
lunny May 20, 2024
1c202fa
Fix test
lunny May 21, 2024
56914ed
Fix test
lunny May 21, 2024
63b74a2
Merge branch 'main' into lunny/fix_automerge
lunny May 21, 2024
a5f0397
Use pull request directly when pull request review approved
lunny May 21, 2024
9680940
Revert "Use pull request directly when pull request review approved"
lunny May 21, 2024
894537c
Update services/automerge/automerge.go
lunny May 21, 2024
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
6 changes: 3 additions & 3 deletions models/issues/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,14 @@ func (r *Review) LoadCodeComments(ctx context.Context) (err error) {
if r.CodeComments != nil {
return err
}
if err = r.loadIssue(ctx); err != nil {
if err = r.LoadIssue(ctx); err != nil {
return err
}
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false)
return err
}

func (r *Review) loadIssue(ctx context.Context) (err error) {
func (r *Review) LoadIssue(ctx context.Context) (err error) {
if r.Issue != nil {
return err
}
Expand Down Expand Up @@ -199,7 +199,7 @@ func (r *Review) LoadReviewerTeam(ctx context.Context) (err error) {

// LoadAttributes loads all attributes except CodeComments
func (r *Review) LoadAttributes(ctx context.Context) (err error) {
if err = r.loadIssue(ctx); err != nil {
if err = r.LoadIssue(ctx); err != nil {
return err
}
if err = r.LoadCodeComments(ctx); err != nil {
Expand Down
111 changes: 72 additions & 39 deletions services/automerge/automerge.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue"
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
)

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

// Init runs the task queue to that handles auto merges
func Init() error {
notify_service.RegisterNotifier(NewNotifier())

prAutoMergeQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_auto_merge", handler)
if prAutoMergeQueue == nil {
return fmt.Errorf("unable to create pr_auto_merge queue")
Expand All @@ -47,7 +50,7 @@ func handler(items ...string) []string {
log.Error("could not parse data from pr_auto_merge queue (%v): %v", s, err)
continue
}
handlePull(id, sha)
handlePullRequestAutoMerge(id, sha)
}
return nil
}
Expand All @@ -62,16 +65,6 @@ func addToQueue(pr *issues_model.PullRequest, sha string) {
// ScheduleAutoMerge if schedule is false and no error, pull can be merged directly
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string) (scheduled bool, err error) {
err = db.WithTx(ctx, func(ctx context.Context) error {
lastCommitStatus, err := pull_service.GetPullRequestCommitStatusState(ctx, pull)
Comment thread
lunny marked this conversation as resolved.
if err != nil {
return err
}

// we don't need to schedule
if lastCommitStatus.IsSuccess() {
return nil
}

if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message); err != nil {
return err
}
Expand All @@ -95,8 +88,8 @@ func RemoveScheduledAutoMerge(ctx context.Context, doer *user_model.User, pull *
})
}

// MergeScheduledPullRequest merges a previously scheduled pull request when all checks succeeded
func MergeScheduledPullRequest(ctx context.Context, sha string, repo *repo_model.Repository) error {
// StartPullRequestAutoMergeCheckBySHA start an automerge check task for repository and SHA
func StartPullRequestAutoMergeCheckBySHA(ctx context.Context, sha string, repo *repo_model.Repository) error {
pulls, err := getPullRequestsByHeadSHA(ctx, sha, repo, func(pr *issues_model.PullRequest) bool {
return !pr.HasMerged && pr.CanAutoMerge()
})
Expand All @@ -111,14 +104,41 @@ func MergeScheduledPullRequest(ctx context.Context, sha string, repo *repo_model
return nil
}

// StartPullRequestAutoMergeCheck start an automerge check task for a pull request
func StartPullRequestAutoMergeCheck(ctx context.Context, pull *issues_model.PullRequest) {
if pull == nil || pull.HasMerged || !pull.CanAutoMerge() {
return
}

if err := pull.LoadBaseRepo(ctx); err != nil {
log.Error("LoadBaseRepo: %v", err)
return
}

gitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo)
if err != nil {
log.Error("OpenRepository: %v", err)
return
}

commitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
gitRepo.Close()
Comment thread
lunny marked this conversation as resolved.
Outdated
if err != nil {
log.Error("GetRefCommitID: %v", err)
return
}

addToQueue(pull, commitID)
}

func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model.Repository, filter func(*issues_model.PullRequest) bool) (map[int64]*issues_model.PullRequest, error) {
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
return nil, err
}
defer gitRepo.Close()

refs, err := gitRepo.GetRefsBySha(sha, "")
gitRepo.Close()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -161,7 +181,8 @@ func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model.
return pulls, nil
}

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

if err = pr.LoadBaseRepo(ctx); err != nil {
log.Error("%-v LoadBaseRepo: %v", pr, err)
return
}

// check the sha is the same as pull request head commit id
baseGitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
if err != nil {
log.Error("OpenRepository: %v", err)
return
}
defer baseGitRepo.Close()

headCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
log.Error("GetRefCommitID: %v", err)
return
}
if headCommitID != sha {
log.Warn("Head commit id of auto merge %-v does not match sha [%s]", pr, sha)
return
}

// Get all checks for this pr
// We get the latest sha commit hash again to handle the case where the check of a previous push
// did not succeed or was not finished yet.

if err = pr.LoadHeadRepo(ctx); err != nil {
log.Error("%-v LoadHeadRepo: %v", pr, err)
return
}

headGitRepo, err := gitrepo.OpenRepository(ctx, pr.HeadRepo)
if err != nil {
log.Error("OpenRepository %-v: %v", pr.HeadRepo, err)
return
var headGitRepo *git.Repository
if pr.BaseRepoID == pr.HeadRepoID {
headGitRepo = baseGitRepo
} else {
headGitRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
if err != nil {
log.Error("OpenRepository %-v: %v", pr.HeadRepo, err)
return
}
defer headGitRepo.Close()
}
defer headGitRepo.Close()

headBranchExist := headGitRepo.IsBranchExist(pr.HeadBranch)

if pr.HeadRepo == nil || !headBranchExist {
log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch: %s]", pr, pr.HeadRepoID, pr.HeadBranch)
return
Expand Down Expand Up @@ -238,25 +285,11 @@ func handlePull(pullID int64, sha string) {
return
}

var baseGitRepo *git.Repository
if pr.BaseRepoID == pr.HeadRepoID {
baseGitRepo = headGitRepo
} else {
if err = pr.LoadBaseRepo(ctx); err != nil {
log.Error("%-v LoadBaseRepo: %v", pr, err)
return
}

baseGitRepo, err = gitrepo.OpenRepository(ctx, pr.BaseRepo)
if err != nil {
log.Error("OpenRepository %-v: %v", pr.BaseRepo, err)
return
}
defer baseGitRepo.Close()
}

if err := pull_service.Merge(ctx, pr, doer, baseGitRepo, scheduledPRM.MergeStyle, "", scheduledPRM.Message, true); err != nil {
log.Error("pull_service.Merge: %v", err)
// FIXME: if merge failed, we should display some error message to the pull request page.
// The resolution is add a new column on automerge table named `error_message` to store the error message and displayed
// on the pull request page. But this should not be finished in a bug fix PR which will be backport to release branch.
return
}
}
46 changes: 46 additions & 0 deletions services/automerge/notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package automerge

import (
"context"

issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
notify_service "code.gitea.io/gitea/services/notify"
)

type automergeNotifier struct {
notify_service.NullNotifier
}

var _ notify_service.Notifier = &automergeNotifier{}

// NewNotifier create a new automergeNotifier notifier
func NewNotifier() notify_service.Notifier {
return &automergeNotifier{}
}

func (n *automergeNotifier) PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
// as a missing / blocking reviews could have blocked a pending automerge let's recheck
if review.Type == issues_model.ReviewTypeApprove {
if err := StartPullRequestAutoMergeCheckBySHA(ctx, review.CommitID, pr.BaseRepo); err != nil {
log.Error("StartPullRequestAutoMergeCheckBySHA: %v", err)
}
}
}

func (n *automergeNotifier) PullReviewDismiss(ctx context.Context, doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
if err := review.LoadIssue(ctx); err != nil {
log.Error("LoadIssue: %v", err)
return
}
if err := review.Issue.LoadPullRequest(ctx); err != nil {
log.Error("LoadPullRequest: %v", err)
return
}
// as reviews could have blocked a pending automerge let's recheck
StartPullRequestAutoMergeCheck(ctx, review.Issue.PullRequest)
}
1 change: 0 additions & 1 deletion services/pull/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,6 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use
return "", err
}
defer cancel()

// Merge commits.
switch mergeStyle {
case repo_model.MergeStyleMerge:
Expand Down
2 changes: 1 addition & 1 deletion services/repository/commitstatus/commitstatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
}

if status.State.IsSuccess() {
if err := automerge.MergeScheduledPullRequest(ctx, sha, repo); err != nil {
if err := automerge.StartPullRequestAutoMergeCheckBySHA(ctx, sha, repo); err != nil {
return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
}
}
Expand Down