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
pull/9924/head^2
Lunny Xiao 2020-01-22 11:46:04 +08:00 committed by GitHub
parent cca13ae2ac
commit 81daf26878
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 159 additions and 84 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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())
}

View File

@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
package structs // import "code.gitea.io/gitea/modules/structs"
import (
"time"
)

View 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
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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"}}