diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go index a1ee204882..fcc013348c 100644 --- a/services/automerge/automerge.go +++ b/services/automerge/automerge.go @@ -7,8 +7,6 @@ import ( "context" "errors" "fmt" - "strconv" - "strings" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" @@ -24,20 +22,18 @@ import ( "code.gitea.io/gitea/modules/queue" notify_service "code.gitea.io/gitea/services/notify" pull_service "code.gitea.io/gitea/services/pull" + shared_automerge "code.gitea.io/gitea/services/shared/automerge" ) -// prAutoMergeQueue represents a queue to handle update pull request tests -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 { + shared_automerge.PRAutoMergeQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_auto_merge", handler) + if shared_automerge.PRAutoMergeQueue == nil { return fmt.Errorf("unable to create pr_auto_merge queue") } - go graceful.GetManager().RunWithCancel(prAutoMergeQueue) + go graceful.GetManager().RunWithCancel(shared_automerge.PRAutoMergeQueue) return nil } @@ -55,13 +51,6 @@ func handler(items ...string) []string { return nil } -func addToQueue(pr *issues_model.PullRequest, sha string) { - log.Trace("Adding pullID: %d to the pull requests patch checking queue with sha %s", pr.ID, sha) - if err := prAutoMergeQueue.Push(fmt.Sprintf("%d_%s", pr.ID, sha)); err != nil { - log.Error("Error adding pullID: %d to the pull requests patch checking queue %v", pr.ID, err) - } -} - // 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 { @@ -90,94 +79,12 @@ func RemoveScheduledAutoMerge(ctx context.Context, doer *user_model.User, pull * // StartPRCheckAndAutoMergeBySHA start an automerge check and auto merge task for all pull requests of repository and SHA func StartPRCheckAndAutoMergeBySHA(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() - }) - if err != nil { - return err - } - - for _, pr := range pulls { - addToQueue(pr, sha) - } - - return nil + return shared_automerge.StartPRCheckAndAutoMergeBySHA(ctx, sha, repo) } // StartPRCheckAndAutoMerge start an automerge check and auto merge task for a pull request func StartPRCheckAndAutoMerge(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 - } - defer gitRepo.Close() - commitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName()) - 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, "") - if err != nil { - return nil, err - } - - pulls := make(map[int64]*issues_model.PullRequest) - - for _, ref := range refs { - // Each pull branch starts with refs/pull/ we then go from there to find the index of the pr and then - // use that to get the pr. - if strings.HasPrefix(ref, git.PullPrefix) { - parts := strings.Split(ref[len(git.PullPrefix):], "/") - - // e.g. 'refs/pull/1/head' would be []string{"1", "head"} - if len(parts) != 2 { - log.Error("getPullRequestsByHeadSHA found broken pull ref [%s] on repo [%-v]", ref, repo) - continue - } - - prIndex, err := strconv.ParseInt(parts[0], 10, 64) - if err != nil { - log.Error("getPullRequestsByHeadSHA found broken pull ref [%s] on repo [%-v]", ref, repo) - continue - } - - p, err := issues_model.GetPullRequestByIndex(ctx, repo.ID, prIndex) - if err != nil { - // If there is no pull request for this branch, we don't try to merge it. - if issues_model.IsErrPullRequestNotExist(err) { - continue - } - return nil, err - } - - if filter(p) { - pulls[p.ID] = p - } - } - } - - return pulls, nil + shared_automerge.StartPRCheckAndAutoMerge(ctx, pull) } // handlePullRequestAutoMerge merge the pull request if all checks are successful diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go index 5c630201d2..635b0b108e 100644 --- a/services/repository/commitstatus/commitstatus.go +++ b/services/repository/commitstatus/commitstatus.go @@ -19,7 +19,7 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/services/automerge" + shared_automerge "code.gitea.io/gitea/services/shared/automerge" ) func getCacheKey(repoID int64, brancheName string) string { @@ -117,7 +117,7 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato } if status.State.IsSuccess() { - if err := automerge.StartPRCheckAndAutoMergeBySHA(ctx, sha, repo); err != nil { + if err := shared_automerge.StartPRCheckAndAutoMergeBySHA(ctx, sha, repo); err != nil { return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err) } } diff --git a/services/shared/automerge/automerge.go b/services/shared/automerge/automerge.go new file mode 100644 index 0000000000..8f38cf260a --- /dev/null +++ b/services/shared/automerge/automerge.go @@ -0,0 +1,120 @@ +// Copyright 2021 Gitea. All rights reserved. +// SPDX-License-Identifier: MIT + +package automerge + +import ( + "context" + "fmt" + "strconv" + "strings" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" +) + +// PRAutoMergeQueue represents a queue to handle update pull request tests +var PRAutoMergeQueue *queue.WorkerPoolQueue[string] + +func addToQueue(pr *issues_model.PullRequest, sha string) { + log.Trace("Adding pullID: %d to the pull requests patch checking queue with sha %s", pr.ID, sha) + if err := PRAutoMergeQueue.Push(fmt.Sprintf("%d_%s", pr.ID, sha)); err != nil { + log.Error("Error adding pullID: %d to the pull requests patch checking queue %v", pr.ID, err) + } +} + +// StartPRCheckAndAutoMergeBySHA start an automerge check and auto merge task for all pull requests of repository and SHA +func StartPRCheckAndAutoMergeBySHA(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() + }) + if err != nil { + return err + } + + for _, pr := range pulls { + addToQueue(pr, sha) + } + + return nil +} + +// StartPRCheckAndAutoMerge start an automerge check and auto merge task for a pull request +func StartPRCheckAndAutoMerge(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 + } + defer gitRepo.Close() + commitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName()) + 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, "") + if err != nil { + return nil, err + } + + pulls := make(map[int64]*issues_model.PullRequest) + + for _, ref := range refs { + // Each pull branch starts with refs/pull/ we then go from there to find the index of the pr and then + // use that to get the pr. + if strings.HasPrefix(ref, git.PullPrefix) { + parts := strings.Split(ref[len(git.PullPrefix):], "/") + + // e.g. 'refs/pull/1/head' would be []string{"1", "head"} + if len(parts) != 2 { + log.Error("getPullRequestsByHeadSHA found broken pull ref [%s] on repo [%-v]", ref, repo) + continue + } + + prIndex, err := strconv.ParseInt(parts[0], 10, 64) + if err != nil { + log.Error("getPullRequestsByHeadSHA found broken pull ref [%s] on repo [%-v]", ref, repo) + continue + } + + p, err := issues_model.GetPullRequestByIndex(ctx, repo.ID, prIndex) + if err != nil { + // If there is no pull request for this branch, we don't try to merge it. + if issues_model.IsErrPullRequestNotExist(err) { + continue + } + return nil, err + } + + if filter(p) { + pulls[p.ID] = p + } + } + } + + return pulls, nil +}