Fix wrong hint when status checking is running on pull request view (#9886)
* Fix wrong hint when status checking is running on pull request view * fix lint * fix test * fix test * fix wrong tmpl * fix import * rename function name
This commit is contained in:
parent
cca13ae2ac
commit
81daf26878
9 changed files with 159 additions and 84 deletions
|
@ -11,7 +11,6 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -48,20 +47,20 @@ func TestPullCreate_CommitStatus(t *testing.T) {
|
|||
|
||||
commitID := path.Base(commitURL)
|
||||
|
||||
statusList := []models.CommitStatusState{
|
||||
models.CommitStatusPending,
|
||||
models.CommitStatusError,
|
||||
models.CommitStatusFailure,
|
||||
models.CommitStatusWarning,
|
||||
models.CommitStatusSuccess,
|
||||
statusList := []api.CommitStatusState{
|
||||
api.CommitStatusPending,
|
||||
api.CommitStatusError,
|
||||
api.CommitStatusFailure,
|
||||
api.CommitStatusWarning,
|
||||
api.CommitStatusSuccess,
|
||||
}
|
||||
|
||||
statesIcons := map[models.CommitStatusState]string{
|
||||
models.CommitStatusPending: "circle icon yellow",
|
||||
models.CommitStatusSuccess: "check icon green",
|
||||
models.CommitStatusError: "warning icon red",
|
||||
models.CommitStatusFailure: "remove icon red",
|
||||
models.CommitStatusWarning: "warning sign icon yellow",
|
||||
statesIcons := map[api.CommitStatusState]string{
|
||||
api.CommitStatusPending: "circle icon yellow",
|
||||
api.CommitStatusSuccess: "check icon green",
|
||||
api.CommitStatusError: "warning icon red",
|
||||
api.CommitStatusFailure: "remove icon red",
|
||||
api.CommitStatusWarning: "warning sign icon yellow",
|
||||
}
|
||||
|
||||
// Update commit status, and check if icon is updated as well
|
||||
|
|
|
@ -19,52 +19,19 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// CommitStatusState holds the state of a Status
|
||||
// It can be "pending", "success", "error", "failure", and "warning"
|
||||
type CommitStatusState string
|
||||
|
||||
// IsWorseThan returns true if this State is worse than the given State
|
||||
func (css CommitStatusState) IsWorseThan(css2 CommitStatusState) bool {
|
||||
switch css {
|
||||
case CommitStatusError:
|
||||
return true
|
||||
case CommitStatusFailure:
|
||||
return css2 != CommitStatusError
|
||||
case CommitStatusWarning:
|
||||
return css2 != CommitStatusError && css2 != CommitStatusFailure
|
||||
case CommitStatusSuccess:
|
||||
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning
|
||||
default:
|
||||
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning && css2 != CommitStatusSuccess
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// CommitStatusPending is for when the Status is Pending
|
||||
CommitStatusPending CommitStatusState = "pending"
|
||||
// CommitStatusSuccess is for when the Status is Success
|
||||
CommitStatusSuccess CommitStatusState = "success"
|
||||
// CommitStatusError is for when the Status is Error
|
||||
CommitStatusError CommitStatusState = "error"
|
||||
// CommitStatusFailure is for when the Status is Failure
|
||||
CommitStatusFailure CommitStatusState = "failure"
|
||||
// CommitStatusWarning is for when the Status is Warning
|
||||
CommitStatusWarning CommitStatusState = "warning"
|
||||
)
|
||||
|
||||
// CommitStatus holds a single Status of a single Commit
|
||||
type CommitStatus struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
Repo *Repository `xorm:"-"`
|
||||
State CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
|
||||
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
|
||||
TargetURL string `xorm:"TEXT"`
|
||||
Description string `xorm:"TEXT"`
|
||||
ContextHash string `xorm:"char(40) index"`
|
||||
Context string `xorm:"TEXT"`
|
||||
Creator *User `xorm:"-"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
Repo *Repository `xorm:"-"`
|
||||
State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
|
||||
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
|
||||
TargetURL string `xorm:"TEXT"`
|
||||
Description string `xorm:"TEXT"`
|
||||
ContextHash string `xorm:"char(40) index"`
|
||||
Context string `xorm:"TEXT"`
|
||||
Creator *User `xorm:"-"`
|
||||
CreatorID int64
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
|
@ -118,9 +85,9 @@ func (status *CommitStatus) APIFormat() *api.Status {
|
|||
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
|
||||
func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus {
|
||||
var lastStatus *CommitStatus
|
||||
var state CommitStatusState
|
||||
var state api.CommitStatusState
|
||||
for _, status := range statuses {
|
||||
if status.State.IsWorseThan(state) {
|
||||
if status.State.NoBetterThan(state) {
|
||||
state = status.State
|
||||
lastStatus = status
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package models
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -23,22 +24,22 @@ func TestGetCommitStatuses(t *testing.T) {
|
|||
assert.Len(t, statuses, 5)
|
||||
|
||||
assert.Equal(t, "ci/awesomeness", statuses[0].Context)
|
||||
assert.Equal(t, CommitStatusPending, statuses[0].State)
|
||||
assert.Equal(t, structs.CommitStatusPending, statuses[0].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[0].APIURL())
|
||||
|
||||
assert.Equal(t, "cov/awesomeness", statuses[1].Context)
|
||||
assert.Equal(t, CommitStatusWarning, statuses[1].State)
|
||||
assert.Equal(t, structs.CommitStatusWarning, statuses[1].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[1].APIURL())
|
||||
|
||||
assert.Equal(t, "cov/awesomeness", statuses[2].Context)
|
||||
assert.Equal(t, CommitStatusSuccess, statuses[2].State)
|
||||
assert.Equal(t, structs.CommitStatusSuccess, statuses[2].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[2].APIURL())
|
||||
|
||||
assert.Equal(t, "ci/awesomeness", statuses[3].Context)
|
||||
assert.Equal(t, CommitStatusFailure, statuses[3].State)
|
||||
assert.Equal(t, structs.CommitStatusFailure, statuses[3].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[3].APIURL())
|
||||
|
||||
assert.Equal(t, "deploy/awesomeness", statuses[4].Context)
|
||||
assert.Equal(t, CommitStatusError, statuses[4].State)
|
||||
assert.Equal(t, structs.CommitStatusError, statuses[4].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[4].APIURL())
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
package structs // import "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
|
63
modules/structs/commit_status.go
Normal file
63
modules/structs/commit_status.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package structs
|
||||
|
||||
// CommitStatusState holds the state of a Status
|
||||
// It can be "pending", "success", "error", "failure", and "warning"
|
||||
type CommitStatusState string
|
||||
|
||||
const (
|
||||
// CommitStatusPending is for when the Status is Pending
|
||||
CommitStatusPending CommitStatusState = "pending"
|
||||
// CommitStatusSuccess is for when the Status is Success
|
||||
CommitStatusSuccess CommitStatusState = "success"
|
||||
// CommitStatusError is for when the Status is Error
|
||||
CommitStatusError CommitStatusState = "error"
|
||||
// CommitStatusFailure is for when the Status is Failure
|
||||
CommitStatusFailure CommitStatusState = "failure"
|
||||
// CommitStatusWarning is for when the Status is Warning
|
||||
CommitStatusWarning CommitStatusState = "warning"
|
||||
)
|
||||
|
||||
// NoBetterThan returns true if this State is no better than the given State
|
||||
func (css CommitStatusState) NoBetterThan(css2 CommitStatusState) bool {
|
||||
switch css {
|
||||
case CommitStatusError:
|
||||
return true
|
||||
case CommitStatusFailure:
|
||||
return css2 != CommitStatusError
|
||||
case CommitStatusWarning:
|
||||
return css2 != CommitStatusError && css2 != CommitStatusFailure
|
||||
case CommitStatusPending:
|
||||
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning
|
||||
default:
|
||||
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning && css2 != CommitStatusPending
|
||||
}
|
||||
}
|
||||
|
||||
// IsPending represents if commit status state is pending
|
||||
func (css CommitStatusState) IsPending() bool {
|
||||
return css == CommitStatusPending
|
||||
}
|
||||
|
||||
// IsSuccess represents if commit status state is success
|
||||
func (css CommitStatusState) IsSuccess() bool {
|
||||
return css == CommitStatusSuccess
|
||||
}
|
||||
|
||||
// IsError represents if commit status state is error
|
||||
func (css CommitStatusState) IsError() bool {
|
||||
return css == CommitStatusError
|
||||
}
|
||||
|
||||
// IsFailure represents if commit status state is failure
|
||||
func (css CommitStatusState) IsFailure() bool {
|
||||
return css == CommitStatusFailure
|
||||
}
|
||||
|
||||
// IsWarning represents if commit status state is warning
|
||||
func (css CommitStatusState) IsWarning() bool {
|
||||
return css == CommitStatusWarning
|
||||
}
|
|
@ -53,7 +53,7 @@ func NewCommitStatus(ctx *context.APIContext, form api.CreateStatusOption) {
|
|||
return
|
||||
}
|
||||
status := &models.CommitStatus{
|
||||
State: models.CommitStatusState(form.State),
|
||||
State: api.CommitStatusState(form.State),
|
||||
TargetURL: form.TargetURL,
|
||||
Description: form.Description,
|
||||
Context: form.Context,
|
||||
|
@ -220,13 +220,13 @@ func getCommitStatuses(ctx *context.APIContext, sha string) {
|
|||
}
|
||||
|
||||
type combinedCommitStatus struct {
|
||||
State models.CommitStatusState `json:"state"`
|
||||
SHA string `json:"sha"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Statuses []*api.Status `json:"statuses"`
|
||||
Repo *api.Repository `json:"repository"`
|
||||
CommitURL string `json:"commit_url"`
|
||||
URL string `json:"url"`
|
||||
State api.CommitStatusState `json:"state"`
|
||||
SHA string `json:"sha"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Statuses []*api.Status `json:"statuses"`
|
||||
Repo *api.Repository `json:"repository"`
|
||||
CommitURL string `json:"commit_url"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// GetCombinedCommitStatusByRef returns the combined status for any given commit hash
|
||||
|
@ -293,7 +293,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
|
|||
retStatus.Statuses = make([]*api.Status, 0, len(statuses))
|
||||
for _, status := range statuses {
|
||||
retStatus.Statuses = append(retStatus.Statuses, status.APIFormat())
|
||||
if status.State.IsWorseThan(retStatus.State) {
|
||||
if status.State.NoBetterThan(retStatus.State) {
|
||||
retStatus.State = status.State
|
||||
}
|
||||
}
|
||||
|
|
|
@ -417,7 +417,9 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
|
|||
}
|
||||
return false
|
||||
}
|
||||
ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
|
||||
state := pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
|
||||
ctx.Data["RequiredStatusCheckState"] = state
|
||||
ctx.Data["IsRequiredStatusCheckSuccess"] = state.IsSuccess()
|
||||
}
|
||||
|
||||
ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
|
||||
|
|
|
@ -8,15 +8,47 @@ package pull
|
|||
import (
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// MergeRequiredContextsCommitStatus returns a commit status state for given required contexts
|
||||
func MergeRequiredContextsCommitStatus(commitStatuses []*models.CommitStatus, requiredContexts []string) structs.CommitStatusState {
|
||||
if len(requiredContexts) == 0 {
|
||||
status := models.CalcCommitStatus(commitStatuses)
|
||||
if status != nil {
|
||||
return status.State
|
||||
}
|
||||
return structs.CommitStatusSuccess
|
||||
}
|
||||
|
||||
var returnedStatus = structs.CommitStatusPending
|
||||
for _, ctx := range requiredContexts {
|
||||
var targetStatus structs.CommitStatusState
|
||||
for _, commitStatus := range commitStatuses {
|
||||
if commitStatus.Context == ctx {
|
||||
targetStatus = commitStatus.State
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetStatus == "" {
|
||||
targetStatus = structs.CommitStatusPending
|
||||
}
|
||||
if targetStatus.NoBetterThan(returnedStatus) {
|
||||
returnedStatus = targetStatus
|
||||
}
|
||||
}
|
||||
return returnedStatus
|
||||
}
|
||||
|
||||
// IsCommitStatusContextSuccess returns true if all required status check contexts succeed.
|
||||
func IsCommitStatusContextSuccess(commitStatuses []*models.CommitStatus, requiredContexts []string) bool {
|
||||
// If no specific context is required, require that last commit status is a success
|
||||
if len(requiredContexts) == 0 {
|
||||
status := models.CalcCommitStatus(commitStatuses)
|
||||
if status == nil || status.State != models.CommitStatusSuccess {
|
||||
if status == nil || status.State != structs.CommitStatusSuccess {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -26,7 +58,7 @@ func IsCommitStatusContextSuccess(commitStatuses []*models.CommitStatus, require
|
|||
var found bool
|
||||
for _, commitStatus := range commitStatuses {
|
||||
if commitStatus.Context == ctx {
|
||||
if commitStatus.State != models.CommitStatusSuccess {
|
||||
if commitStatus.State != structs.CommitStatusSuccess {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -50,30 +82,39 @@ func IsPullCommitStatusPass(pr *models.PullRequest) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
state, err := GetPullRequestCommitStatusState(pr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return state.IsSuccess(), nil
|
||||
}
|
||||
|
||||
// GetPullRequestCommitStatusState returns pull request merged commit status state
|
||||
func GetPullRequestCommitStatusState(pr *models.PullRequest) (structs.CommitStatusState, error) {
|
||||
// check if all required status checks are successful
|
||||
headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "OpenRepository")
|
||||
return "", errors.Wrap(err, "OpenRepository")
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
|
||||
if !headGitRepo.IsBranchExist(pr.HeadBranch) {
|
||||
return false, errors.New("Head branch does not exist, can not merge")
|
||||
return "", errors.New("Head branch does not exist, can not merge")
|
||||
}
|
||||
|
||||
sha, err := headGitRepo.GetBranchCommitID(pr.HeadBranch)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "GetBranchCommitID")
|
||||
return "", errors.Wrap(err, "GetBranchCommitID")
|
||||
}
|
||||
|
||||
if err := pr.LoadBaseRepo(); err != nil {
|
||||
return false, errors.Wrap(err, "LoadBaseRepo")
|
||||
return "", errors.Wrap(err, "LoadBaseRepo")
|
||||
}
|
||||
|
||||
commitStatuses, err := models.GetLatestCommitStatus(pr.BaseRepo, sha, 0)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "GetLatestCommitStatus")
|
||||
return "", errors.Wrap(err, "GetLatestCommitStatus")
|
||||
}
|
||||
|
||||
return IsCommitStatusContextSuccess(commitStatuses, pr.ProtectedBranch.StatusCheckContexts), nil
|
||||
return MergeRequiredContextsCommitStatus(commitStatuses, pr.ProtectedBranch.StatusCheckContexts), nil
|
||||
}
|
||||
|
|
|
@ -47,7 +47,8 @@
|
|||
{{else if .IsPullRequestBroken}}red
|
||||
{{else if .IsBlockedByApprovals}}red
|
||||
{{else if .IsBlockedByRejection}}red
|
||||
{{else if and .EnableStatusCheck (not .IsRequiredStatusCheckSuccess)}}red
|
||||
{{else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsFailure .RequiredStatusCheckState.IsError)}}red
|
||||
{{else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsPending .RequiredStatusCheckState.IsWarning)}}yellow
|
||||
{{else if and .RequireSigned (not .WillSign)}}}red
|
||||
{{else if .Issue.PullRequest.IsChecking}}yellow
|
||||
{{else if .Issue.PullRequest.CanAutoMerge}}green
|
||||
|
@ -118,7 +119,7 @@
|
|||
<i class="icon icon-octicon"><span class="octicon octicon-x"></span></i>
|
||||
{{$.i18n.Tr "repo.pulls.blocked_by_rejection"}}
|
||||
</div>
|
||||
{{else if and .EnableStatusCheck (not .IsRequiredStatusCheckSuccess)}}
|
||||
{{else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsError .RequiredStatusCheckState.IsFailure)}}
|
||||
<div class="item text red">
|
||||
<i class="icon icon-octicon"><span class="octicon octicon-x"></span></i>
|
||||
{{$.i18n.Tr "repo.pulls.required_status_check_failed"}}
|
||||
|
|
Reference in a new issue