295cd6be94
- There's no need to use `github.com/pkg/errors` when the standard library already has the functionality to wrap and create errors. (cherry picked from commit 40f603a538036ce7e150adf404374d794eb46993) (cherry picked from commit aa68a2753f204dfe44a037ccc91dacc2a0a53803) (cherry picked from commit 48e252d73961ae17b56544bae79bc812dac10c44) (cherry picked from commit cc6f40ccd208a6fd1527ba08e3123e9b01b218ab) (cherry picked from commit 03c4b9735824bfaed19df14550ef458d52667b40) (cherry picked from commit f25eeb76958b78fbe00fc6556aec8a37aba0ecdf) (cherry picked from commit 989d8fa1cb81e0ff017ac575fdd1cf12507eb42b) (cherry picked from commit 10e890ed8e7205c140677ff74db393fc0052da14) (cherry picked from commit 581519389d3a35e775f7d3fdc15c251a3e0828b6) (cherry picked from commit 03d00b11ac4880fd897631180c40bc1dedf5d7e5) (cherry picked from commit 04e6c853d48b0dab767046c968717f3982304c40) Conflicts: go.mod https://codeberg.org/forgejo/forgejo/pulls/2249 (cherry picked from commit 2c4c29f7bc20e31321c4932998692dd1ef73aa14)
163 lines
4.8 KiB
Go
163 lines
4.8 KiB
Go
// Copyright 2019 The Gitea Authors.
|
|
// All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package pull
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
git_model "code.gitea.io/gitea/models/git"
|
|
issues_model "code.gitea.io/gitea/models/issues"
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/gitrepo"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/structs"
|
|
|
|
"github.com/gobwas/glob"
|
|
)
|
|
|
|
// MergeRequiredContextsCommitStatus returns a commit status state for given required contexts
|
|
func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, requiredContexts []string) structs.CommitStatusState {
|
|
// matchedCount is the number of `CommitStatus.Context` that match any context of `requiredContexts`
|
|
matchedCount := 0
|
|
returnedStatus := structs.CommitStatusSuccess
|
|
|
|
if len(requiredContexts) > 0 {
|
|
requiredContextsGlob := make(map[string]glob.Glob, len(requiredContexts))
|
|
for _, ctx := range requiredContexts {
|
|
if gp, err := glob.Compile(ctx); err != nil {
|
|
log.Error("glob.Compile %s failed. Error: %v", ctx, err)
|
|
} else {
|
|
requiredContextsGlob[ctx] = gp
|
|
}
|
|
}
|
|
|
|
for _, commitStatus := range commitStatuses {
|
|
var targetStatus structs.CommitStatusState
|
|
for _, gp := range requiredContextsGlob {
|
|
if gp.Match(commitStatus.Context) {
|
|
targetStatus = commitStatus.State
|
|
matchedCount++
|
|
break
|
|
}
|
|
}
|
|
|
|
if targetStatus != "" && targetStatus.NoBetterThan(returnedStatus) {
|
|
returnedStatus = targetStatus
|
|
}
|
|
}
|
|
}
|
|
|
|
if matchedCount == 0 {
|
|
status := git_model.CalcCommitStatus(commitStatuses)
|
|
if status != nil {
|
|
return status.State
|
|
}
|
|
return structs.CommitStatusSuccess
|
|
}
|
|
|
|
return returnedStatus
|
|
}
|
|
|
|
// IsCommitStatusContextSuccess returns true if all required status check contexts succeed.
|
|
func IsCommitStatusContextSuccess(commitStatuses []*git_model.CommitStatus, requiredContexts []string) bool {
|
|
// If no specific context is required, require that last commit status is a success
|
|
if len(requiredContexts) == 0 {
|
|
status := git_model.CalcCommitStatus(commitStatuses)
|
|
if status == nil || status.State != structs.CommitStatusSuccess {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
for _, ctx := range requiredContexts {
|
|
var found bool
|
|
for _, commitStatus := range commitStatuses {
|
|
if commitStatus.Context == ctx {
|
|
if commitStatus.State != structs.CommitStatusSuccess {
|
|
return false
|
|
}
|
|
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// IsPullCommitStatusPass returns if all required status checks PASS
|
|
func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) {
|
|
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
|
if err != nil {
|
|
return false, fmt.Errorf("GetFirstMatchProtectedBranchRule: %w", err)
|
|
}
|
|
if pb == nil || !pb.EnableStatusCheck {
|
|
return true, nil
|
|
}
|
|
|
|
state, err := GetPullRequestCommitStatusState(ctx, pr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return state.IsSuccess(), nil
|
|
}
|
|
|
|
// GetPullRequestCommitStatusState returns pull request merged commit status state
|
|
func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (structs.CommitStatusState, error) {
|
|
// Ensure HeadRepo is loaded
|
|
if err := pr.LoadHeadRepo(ctx); err != nil {
|
|
return "", fmt.Errorf("LoadHeadRepo: %w", err)
|
|
}
|
|
|
|
// check if all required status checks are successful
|
|
headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.HeadRepo)
|
|
if err != nil {
|
|
return "", fmt.Errorf("RepositoryFromContextOrOpen: %w", err)
|
|
}
|
|
defer closer.Close()
|
|
|
|
if pr.Flow == issues_model.PullRequestFlowGithub && !headGitRepo.IsBranchExist(pr.HeadBranch) {
|
|
return "", errors.New("head branch does not exist, can not merge")
|
|
}
|
|
if pr.Flow == issues_model.PullRequestFlowAGit && !git.IsReferenceExist(ctx, headGitRepo.Path, pr.GetGitRefName()) {
|
|
return "", errors.New("head branch does not exist, can not merge")
|
|
}
|
|
|
|
var sha string
|
|
if pr.Flow == issues_model.PullRequestFlowGithub {
|
|
sha, err = headGitRepo.GetBranchCommitID(pr.HeadBranch)
|
|
} else {
|
|
sha, err = headGitRepo.GetRefCommitID(pr.GetGitRefName())
|
|
}
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := pr.LoadBaseRepo(ctx); err != nil {
|
|
return "", fmt.Errorf("LoadBaseRepo: %w", err)
|
|
}
|
|
|
|
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true})
|
|
if err != nil {
|
|
return "", fmt.Errorf("GetLatestCommitStatus: %w", err)
|
|
}
|
|
|
|
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
|
if err != nil {
|
|
return "", fmt.Errorf("GetFirstMatchProtectedBranchRule: %w", err)
|
|
}
|
|
var requiredContexts []string
|
|
if pb != nil {
|
|
requiredContexts = pb.StatusCheckContexts
|
|
}
|
|
|
|
return MergeRequiredContextsCommitStatus(commitStatuses, requiredContexts), nil
|
|
}
|