Introduce combined status

This commit is contained in:
Lunny Xiao committed 2025-05-24 20:38:05 -07:00
1 parent 8365365c9c
commit 1406b01095
28 files changed
+340 -271

No files matched your search

+6 -6
View File
@@ -14,9 +14,9 @@ import (
git_model "code.gitea.io/gitea/models/git"
user_model "code.gitea.io/gitea/models/user"
actions_module "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/commitstatus"
git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
@@ -147,16 +147,16 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
return commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID.String(), &status)
}
func toCommitStatus(status actions_model.Status) api.CommitStatusState {
func toCommitStatus(status actions_model.Status) commitstatus.CommitStatusState {
switch status {
case actions_model.StatusSuccess, actions_model.StatusSkipped:
return api.CommitStatusSuccess
return commitstatus.CommitStatusSuccess
case actions_model.StatusFailure, actions_model.StatusCancelled:
return api.CommitStatusFailure
return commitstatus.CommitStatusFailure
case actions_model.StatusWaiting, actions_model.StatusBlocked, actions_model.StatusRunning:
return api.CommitStatusPending
return commitstatus.CommitStatusPending
default:
return api.CommitStatusError
return commitstatus.CommitStatusError
}
}
+4 -13
View File
@@ -8,6 +8,7 @@ import (
git_model "code.gitea.io/gitea/models/git"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/commitstatus"
api "code.gitea.io/gitea/modules/structs"
)
@@ -45,21 +46,11 @@ func ToCombinedStatus(ctx context.Context, statuses []*git_model.CommitStatus, r
URL: "",
}
retStatus.Statuses = make([]*api.CommitStatus, 0, len(statuses))
states := make(commitstatus.CommitStatusStates, 0, len(statuses))
for _, status := range statuses {
retStatus.Statuses = append(retStatus.Statuses, ToCommitStatus(ctx, status))
if retStatus.State == "" || status.State.NoBetterThan(retStatus.State) {
retStatus.State = status.State
}
states = append(states, status.State)
}
// According to https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference
// > Additionally, a combined state is returned. The state is one of:
// > failure if any of the contexts report as error or failure
// > pending if there are no statuses or a context is pending
// > success if the latest status for all contexts is success
if retStatus.State.IsError() {
retStatus.State = api.CommitStatusFailure
}
retStatus.State = states.Merge()
return retStatus
}
+1 -1
View File
@@ -90,7 +90,7 @@ func ParseCommitsWithStatus(ctx context.Context, oldCommits []*asymkey_model.Sig
}
commit.Statuses = statuses
commit.Status = git_model.CalcCommitStatus(statuses)
commit.Status = git_model.CalcCommitStatusSummary(statuses)
newCommits = append(newCommits, commit)
}
return newCommits, nil
+11 -37
View File
@@ -10,20 +10,16 @@ import (
"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/commitstatus"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
"github.com/gobwas/glob"
"github.com/pkg/errors"
)
// 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
func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, requiredContexts []string) commitstatus.CombinedStatus {
if len(requiredContexts) > 0 {
requiredContextsGlob := make(map[string]glob.Glob, len(requiredContexts))
for _, ctx := range requiredContexts {
@@ -34,57 +30,35 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
}
}
requiredCommitStatuses := make([]*git_model.CommitStatus, 0, len(commitStatuses))
for _, gp := range requiredContextsGlob {
var targetStatus structs.CommitStatusState
for _, commitStatus := range commitStatuses {
if gp.Match(commitStatus.Context) {
targetStatus = commitStatus.State
matchedCount++
requiredCommitStatuses = append(requiredCommitStatuses, commitStatus)
break
}
}
// If required rule not match any action, then it is pending
if targetStatus == "" {
if structs.CommitStatusPending.NoBetterThan(returnedStatus) {
returnedStatus = structs.CommitStatusPending
}
break
}
if targetStatus.NoBetterThan(returnedStatus) {
returnedStatus = targetStatus
}
}
if len(requiredCommitStatuses) > 0 {
return git_model.CalcCommitStatus(requiredCommitStatuses)
}
}
if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess {
status := git_model.CalcCommitStatus(commitStatuses)
if status != nil {
return status.State
}
return structs.CommitStatusSuccess
}
return returnedStatus
return git_model.CalcCommitStatus(commitStatuses)
}
// 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
return git_model.CalcCommitStatus(commitStatuses) == commitstatus.CombinedStatusSuccess
}
for _, ctx := range requiredContexts {
var found bool
for _, commitStatus := range commitStatuses {
if commitStatus.Context == ctx {
if commitStatus.State != structs.CommitStatusSuccess {
if commitStatus.State != commitstatus.CommitStatusSuccess {
return false
}
@@ -117,7 +91,7 @@ func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (
}
// GetPullRequestCommitStatusState returns pull request merged commit status state
func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (structs.CommitStatusState, error) {
func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (commitstatus.CombinedStatus, error) {
// Ensure HeadRepo is loaded
if err := pr.LoadHeadRepo(ctx); err != nil {
return "", errors.Wrap(err, "LoadHeadRepo")
+22 -22
View File
@@ -8,7 +8,7 @@ import (
"testing"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/commitstatus"
"github.com/stretchr/testify/assert"
)
@@ -16,29 +16,29 @@ import (
func TestMergeRequiredContextsCommitStatus(t *testing.T) {
testCases := [][]*git_model.CommitStatus{
{
{Context: "Build 1", State: structs.CommitStatusSuccess},
{Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 3", State: structs.CommitStatusSuccess},
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 3", State: commitstatus.CommitStatusSuccess},
},
{
{Context: "Build 1", State: structs.CommitStatusSuccess},
{Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusPending},
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2t", State: commitstatus.CommitStatusPending},
},
{
{Context: "Build 1", State: structs.CommitStatusSuccess},
{Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusFailure},
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2t", State: commitstatus.CommitStatusFailure},
},
{
{Context: "Build 1", State: structs.CommitStatusSuccess},
{Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusSuccess},
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2t", State: commitstatus.CommitStatusSuccess},
},
{
{Context: "Build 1", State: structs.CommitStatusSuccess},
{Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusSuccess},
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2t", State: commitstatus.CommitStatusSuccess},
},
}
testCasesRequiredContexts := [][]string{
@@ -49,12 +49,12 @@ func TestMergeRequiredContextsCommitStatus(t *testing.T) {
{"Build*", "Build *", "Build 2t*", "Build 1*"},
}
testCasesExpected := []structs.CommitStatusState{
structs.CommitStatusSuccess,
structs.CommitStatusPending,
structs.CommitStatusFailure,
structs.CommitStatusPending,
structs.CommitStatusSuccess,
testCasesExpected := []commitstatus.CombinedStatus{
commitstatus.CombinedStatusSuccess,
commitstatus.CombinedStatusPending,
commitstatus.CombinedStatusFailure,
commitstatus.CombinedStatusPending,
commitstatus.CombinedStatusSuccess,
}
for i, commitStatuses := range testCases {
+8 -9
View File
@@ -946,13 +946,13 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
}
// GetIssuesLastCommitStatus returns a map of issue ID to the most recent commit's latest status
func GetIssuesLastCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64]*git_model.CommitStatus, error) {
func GetIssuesLastCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64]*git_model.CommitStatusSummary, error) {
_, lastStatus, err := GetIssuesAllCommitStatus(ctx, issues)
return lastStatus, err
}
// GetIssuesAllCommitStatus returns a map of issue ID to a list of all statuses for the most recent commit as well as a map of issue ID to only the commit's latest status
func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64][]*git_model.CommitStatus, map[int64]*git_model.CommitStatus, error) {
func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64][]*git_model.CommitStatus, map[int64]*git_model.CommitStatusSummary, error) {
if err := issues.LoadPullRequests(ctx); err != nil {
return nil, nil, err
}
@@ -963,7 +963,7 @@ func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList
var (
gitRepos = make(map[int64]*git.Repository)
res = make(map[int64][]*git_model.CommitStatus)
lastRes = make(map[int64]*git_model.CommitStatus)
lastRes = make(map[int64]*git_model.CommitStatusSummary)
err error
)
defer func() {
@@ -986,27 +986,26 @@ func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList
gitRepos[issue.RepoID] = gitRepo
}
statuses, lastStatus, err := getAllCommitStatus(ctx, gitRepo, issue.PullRequest)
statuses, err := getAllCommitStatus(ctx, gitRepo, issue.PullRequest)
if err != nil {
log.Error("getAllCommitStatus: cant get commit statuses of pull [%d]: %v", issue.PullRequest.ID, err)
continue
}
res[issue.PullRequest.ID] = statuses
lastRes[issue.PullRequest.ID] = lastStatus
lastRes[issue.PullRequest.ID] = git_model.CalcCommitStatusSummary(statuses)
}
return res, lastRes, nil
}
// getAllCommitStatus get pr's commit statuses.
func getAllCommitStatus(ctx context.Context, gitRepo *git.Repository, pr *issues_model.PullRequest) (statuses []*git_model.CommitStatus, lastStatus *git_model.CommitStatus, err error) {
func getAllCommitStatus(ctx context.Context, gitRepo *git.Repository, pr *issues_model.PullRequest) (statuses []*git_model.CommitStatus, err error) {
sha, shaErr := gitRepo.GetRefCommitID(pr.GetGitRefName())
if shaErr != nil {
return nil, nil, shaErr
return nil, shaErr
}
statuses, _, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
lastStatus = git_model.CalcCommitStatus(statuses)
return statuses, lastStatus, err
return statuses, err
}
// IsHeadEqualWithBranch returns if the commits of branchName are available in pull request head
@@ -14,12 +14,12 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/notify"
)
@@ -47,10 +47,10 @@ func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheVal
return nil
}
func updateCommitStatusCache(repoID int64, branchName string, state api.CommitStatusState, targetURL string) error {
func updateCommitStatusCache(repoID int64, branchName string, state commitstatus.CombinedStatus, targetURL string) error {
c := cache.GetCache()
bs, err := json.Marshal(commitStatusCacheValue{
State: state.String(),
State: string(state),
TargetURL: targetURL,
})
if err != nil {
@@ -121,13 +121,13 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
}
// FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
results := make([]*git_model.CommitStatus, len(repos))
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatusSummary, error) {
results := make([]*git_model.CommitStatusSummary, len(repos))
allCached := true
for i, repo := range repos {
if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil {
results[i] = &git_model.CommitStatus{
State: api.CommitStatusState(cv.State),
results[i] = &git_model.CommitStatusSummary{
State: commitstatus.CombinedStatus(cv.State),
TargetURL: cv.TargetURL,
}
} else {
@@ -170,10 +170,8 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
repoSHAs = slices.DeleteFunc(repoSHAs, func(repoSHA git_model.RepoSHA) bool {
return repoSHA.RepoID == repo.ID
})
if results[i] != nil {
if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil {
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
}
if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil {
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
}
break
}
@@ -191,7 +189,7 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
for i, repo := range repos {
if results[i] == nil {
results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
results[i] = git_model.CalcCommitStatusSummary(repoToItsLatestCommitStatuses[repo.ID])
if results[i] != nil {
if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil {
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
+2 -2
View File
@@ -125,7 +125,7 @@ func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
} else {
c.Status = git_model.CalcCommitStatus(statuses)
c.Status = git_model.CalcCommitStatusSummary(statuses)
}
}
return nil
@@ -249,7 +249,7 @@ type Commit struct {
Commit *git.Commit
User *user_model.User
Verification *asymkey_model.CommitVerification
Status *git_model.CommitStatus
Status *git_model.CommitStatusSummary
Flow int64
Row int
Column int
+3 -3
View File
@@ -34,9 +34,9 @@ import (
// WebSearchRepository represents a repository returned by web search
type WebSearchRepository struct {
Repository *structs.Repository `json:"repository"`
LatestCommitStatus *git.CommitStatus `json:"latest_commit_status"`
LocaleLatestCommitStatus string `json:"locale_latest_commit_status"`
Repository *structs.Repository `json:"repository"`
LatestCommitStatus *git.CommitStatusSummary `json:"latest_commit_status"`
LocaleLatestCommitStatus string `json:"locale_latest_commit_status"`
}
// WebSearchResults results of a successful web search