* 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 namemaster
@@ -11,7 +11,6 @@ import ( | |||||
"strings" | "strings" | ||||
"testing" | "testing" | ||||
"code.gitea.io/gitea/models" | |||||
api "code.gitea.io/gitea/modules/structs" | api "code.gitea.io/gitea/modules/structs" | ||||
"github.com/stretchr/testify/assert" | "github.com/stretchr/testify/assert" | ||||
@@ -48,20 +47,20 @@ func TestPullCreate_CommitStatus(t *testing.T) { | |||||
commitID := path.Base(commitURL) | 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 | // Update commit status, and check if icon is updated as well | ||||
@@ -19,52 +19,19 @@ import ( | |||||
"xorm.io/xorm" | "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 | // CommitStatus holds a single Status of a single Commit | ||||
type CommitStatus struct { | 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 | CreatorID int64 | ||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | 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 | // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc | ||||
func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus { | func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus { | ||||
var lastStatus *CommitStatus | var lastStatus *CommitStatus | ||||
var state CommitStatusState | |||||
var state api.CommitStatusState | |||||
for _, status := range statuses { | for _, status := range statuses { | ||||
if status.State.IsWorseThan(state) { | |||||
if status.State.NoBetterThan(state) { | |||||
state = status.State | state = status.State | ||||
lastStatus = status | lastStatus = status | ||||
} | } | ||||
@@ -7,6 +7,7 @@ package models | |||||
import ( | import ( | ||||
"testing" | "testing" | ||||
"code.gitea.io/gitea/modules/structs" | |||||
"github.com/stretchr/testify/assert" | "github.com/stretchr/testify/assert" | ||||
) | ) | ||||
@@ -23,22 +24,22 @@ func TestGetCommitStatuses(t *testing.T) { | |||||
assert.Len(t, statuses, 5) | assert.Len(t, statuses, 5) | ||||
assert.Equal(t, "ci/awesomeness", statuses[0].Context) | 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, "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, "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, "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, "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, "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, "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, "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, "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()) | 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. | // license that can be found in the LICENSE file. | ||||
package structs // import "code.gitea.io/gitea/modules/structs" | package structs // import "code.gitea.io/gitea/modules/structs" | ||||
import ( | import ( | ||||
"time" | "time" | ||||
) | ) | ||||
@@ -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 | return | ||||
} | } | ||||
status := &models.CommitStatus{ | status := &models.CommitStatus{ | ||||
State: models.CommitStatusState(form.State), | |||||
State: api.CommitStatusState(form.State), | |||||
TargetURL: form.TargetURL, | TargetURL: form.TargetURL, | ||||
Description: form.Description, | Description: form.Description, | ||||
Context: form.Context, | Context: form.Context, | ||||
@@ -220,13 +220,13 @@ func getCommitStatuses(ctx *context.APIContext, sha string) { | |||||
} | } | ||||
type combinedCommitStatus struct { | 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 | // 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)) | retStatus.Statuses = make([]*api.Status, 0, len(statuses)) | ||||
for _, status := range statuses { | for _, status := range statuses { | ||||
retStatus.Statuses = append(retStatus.Statuses, status.APIFormat()) | retStatus.Statuses = append(retStatus.Statuses, status.APIFormat()) | ||||
if status.State.IsWorseThan(retStatus.State) { | |||||
if status.State.NoBetterThan(retStatus.State) { | |||||
retStatus.State = status.State | retStatus.State = status.State | ||||
} | } | ||||
} | } | ||||
@@ -417,7 +417,9 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare | |||||
} | } | ||||
return false | 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 | ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha | ||||
@@ -8,15 +8,47 @@ package pull | |||||
import ( | import ( | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/git" | "code.gitea.io/gitea/modules/git" | ||||
"code.gitea.io/gitea/modules/structs" | |||||
"github.com/pkg/errors" | "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. | // IsCommitStatusContextSuccess returns true if all required status check contexts succeed. | ||||
func IsCommitStatusContextSuccess(commitStatuses []*models.CommitStatus, requiredContexts []string) bool { | func IsCommitStatusContextSuccess(commitStatuses []*models.CommitStatus, requiredContexts []string) bool { | ||||
// If no specific context is required, require that last commit status is a success | // If no specific context is required, require that last commit status is a success | ||||
if len(requiredContexts) == 0 { | if len(requiredContexts) == 0 { | ||||
status := models.CalcCommitStatus(commitStatuses) | status := models.CalcCommitStatus(commitStatuses) | ||||
if status == nil || status.State != models.CommitStatusSuccess { | |||||
if status == nil || status.State != structs.CommitStatusSuccess { | |||||
return false | return false | ||||
} | } | ||||
return true | return true | ||||
@@ -26,7 +58,7 @@ func IsCommitStatusContextSuccess(commitStatuses []*models.CommitStatus, require | |||||
var found bool | var found bool | ||||
for _, commitStatus := range commitStatuses { | for _, commitStatus := range commitStatuses { | ||||
if commitStatus.Context == ctx { | if commitStatus.Context == ctx { | ||||
if commitStatus.State != models.CommitStatusSuccess { | |||||
if commitStatus.State != structs.CommitStatusSuccess { | |||||
return false | return false | ||||
} | } | ||||
@@ -50,30 +82,39 @@ func IsPullCommitStatusPass(pr *models.PullRequest) (bool, error) { | |||||
return true, nil | 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 | // check if all required status checks are successful | ||||
headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath()) | headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath()) | ||||
if err != nil { | if err != nil { | ||||
return false, errors.Wrap(err, "OpenRepository") | |||||
return "", errors.Wrap(err, "OpenRepository") | |||||
} | } | ||||
defer headGitRepo.Close() | defer headGitRepo.Close() | ||||
if !headGitRepo.IsBranchExist(pr.HeadBranch) { | 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) | sha, err := headGitRepo.GetBranchCommitID(pr.HeadBranch) | ||||
if err != nil { | if err != nil { | ||||
return false, errors.Wrap(err, "GetBranchCommitID") | |||||
return "", errors.Wrap(err, "GetBranchCommitID") | |||||
} | } | ||||
if err := pr.LoadBaseRepo(); err != nil { | 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) | commitStatuses, err := models.GetLatestCommitStatus(pr.BaseRepo, sha, 0) | ||||
if err != nil { | 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 .IsPullRequestBroken}}red | ||||
{{else if .IsBlockedByApprovals}}red | {{else if .IsBlockedByApprovals}}red | ||||
{{else if .IsBlockedByRejection}}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 and .RequireSigned (not .WillSign)}}}red | ||||
{{else if .Issue.PullRequest.IsChecking}}yellow | {{else if .Issue.PullRequest.IsChecking}}yellow | ||||
{{else if .Issue.PullRequest.CanAutoMerge}}green | {{else if .Issue.PullRequest.CanAutoMerge}}green | ||||
@@ -118,7 +119,7 @@ | |||||
<i class="icon icon-octicon"><span class="octicon octicon-x"></span></i> | <i class="icon icon-octicon"><span class="octicon octicon-x"></span></i> | ||||
{{$.i18n.Tr "repo.pulls.blocked_by_rejection"}} | {{$.i18n.Tr "repo.pulls.blocked_by_rejection"}} | ||||
</div> | </div> | ||||
{{else if and .EnableStatusCheck (not .IsRequiredStatusCheckSuccess)}} | |||||
{{else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsError .RequiredStatusCheckState.IsFailure)}} | |||||
<div class="item text red"> | <div class="item text red"> | ||||
<i class="icon icon-octicon"><span class="octicon octicon-x"></span></i> | <i class="icon icon-octicon"><span class="octicon octicon-x"></span></i> | ||||
{{$.i18n.Tr "repo.pulls.required_status_check_failed"}} | {{$.i18n.Tr "repo.pulls.required_status_check_failed"}} | ||||