Skip to content

Commit 0cec4b8

Browse files
badheziwxiaoguang
andauthored
Fix actions skipped commit status indicator (#34507)
Addresses #34500 Co-authored-by: wxiaoguang <[email protected]>
1 parent c6e2093 commit 0cec4b8

File tree

10 files changed

+112
-249
lines changed

10 files changed

+112
-249
lines changed

models/git/commit_status.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,18 +230,24 @@ func (status *CommitStatus) HideActionsURL(ctx context.Context) {
230230

231231
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
232232
func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus {
233+
// This function is widely used, but it is not quite right.
234+
// Ideally it should return something like "CommitStatusSummary" with properly aggregated state.
235+
// GitHub's behavior: if all statuses are "skipped", GitHub will return "success" as the combined status.
233236
var lastStatus *CommitStatus
234237
state := api.CommitStatusSuccess
235238
for _, status := range statuses {
236-
if status.State.NoBetterThan(state) {
239+
if state == status.State || status.State.HasHigherPriorityThan(state) {
237240
state = status.State
238241
lastStatus = status
239242
}
240243
}
241244
if lastStatus == nil {
242245
if len(statuses) > 0 {
246+
// FIXME: a bad case: Gitea just returns the first commit status, its status is "skipped" in this case.
243247
lastStatus = statuses[0]
244248
} else {
249+
// FIXME: another bad case: if the "statuses" slice is empty, the returned value is an invalid CommitStatus, all its fields are empty.
250+
// Frontend code (tmpl&vue) sometimes depend on the empty fields to skip rendering commit status elements (need to double check in the future)
245251
lastStatus = &CommitStatus{}
246252
}
247253
}

models/git/commit_status_summary.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ func UpdateCommitStatusSummary(ctx context.Context, repoID int64, sha string) er
5959
if err != nil {
6060
return err
6161
}
62-
state := CalcCommitStatus(commitStatuses)
62+
// it guarantees that commitStatuses is not empty because this function is always called after a commit status is created
63+
if len(commitStatuses) == 0 {
64+
setting.PanicInDevOrTesting("no commit statuses found for repo %d and sha %s", repoID, sha)
65+
}
66+
state := CalcCommitStatus(commitStatuses) // non-empty commitStatuses is guaranteed
6367
// mysql will return 0 when update a record which state hasn't been changed which behaviour is different from other database,
6468
// so we need to use insert in on duplicate
6569
if setting.Database.Type.IsMySQL() {

modules/structs/commit_status.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const (
1818
CommitStatusFailure CommitStatusState = "failure"
1919
// CommitStatusWarning is for when the CommitStatus is Warning
2020
CommitStatusWarning CommitStatusState = "warning"
21+
// CommitStatusSkipped is for when CommitStatus is Skipped
22+
CommitStatusSkipped CommitStatusState = "skipped"
2123
)
2224

2325
var commitStatusPriorities = map[CommitStatusState]int{
@@ -26,25 +28,17 @@ var commitStatusPriorities = map[CommitStatusState]int{
2628
CommitStatusWarning: 2,
2729
CommitStatusPending: 3,
2830
CommitStatusSuccess: 4,
31+
CommitStatusSkipped: 5,
2932
}
3033

3134
func (css CommitStatusState) String() string {
3235
return string(css)
3336
}
3437

35-
// NoBetterThan returns true if this State is no better than the given State
36-
// This function only handles the states defined in CommitStatusPriorities
37-
func (css CommitStatusState) NoBetterThan(css2 CommitStatusState) bool {
38-
// NoBetterThan only handles the 5 states above
39-
if _, exist := commitStatusPriorities[css]; !exist {
40-
return false
41-
}
42-
43-
if _, exist := commitStatusPriorities[css2]; !exist {
44-
return false
45-
}
46-
47-
return commitStatusPriorities[css] <= commitStatusPriorities[css2]
38+
// HasHigherPriorityThan returns true if this state has higher priority than the other
39+
// Undefined states are considered to have the highest priority like CommitStatusError(0)
40+
func (css CommitStatusState) HasHigherPriorityThan(other CommitStatusState) bool {
41+
return commitStatusPriorities[css] < commitStatusPriorities[other]
4842
}
4943

5044
// IsPending represents if commit status state is pending

modules/structs/commit_status_test.go

Lines changed: 12 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -10,165 +10,21 @@ import (
1010
)
1111

1212
func TestNoBetterThan(t *testing.T) {
13-
type args struct {
14-
css CommitStatusState
15-
css2 CommitStatusState
16-
}
17-
var unExpectedState CommitStatusState
1813
tests := []struct {
19-
name string
20-
args args
21-
want bool
14+
s1, s2 CommitStatusState
15+
higher bool
2216
}{
23-
{
24-
name: "success is no better than success",
25-
args: args{
26-
css: CommitStatusSuccess,
27-
css2: CommitStatusSuccess,
28-
},
29-
want: true,
30-
},
31-
{
32-
name: "success is no better than pending",
33-
args: args{
34-
css: CommitStatusSuccess,
35-
css2: CommitStatusPending,
36-
},
37-
want: false,
38-
},
39-
{
40-
name: "success is no better than failure",
41-
args: args{
42-
css: CommitStatusSuccess,
43-
css2: CommitStatusFailure,
44-
},
45-
want: false,
46-
},
47-
{
48-
name: "success is no better than error",
49-
args: args{
50-
css: CommitStatusSuccess,
51-
css2: CommitStatusError,
52-
},
53-
want: false,
54-
},
55-
{
56-
name: "pending is no better than success",
57-
args: args{
58-
css: CommitStatusPending,
59-
css2: CommitStatusSuccess,
60-
},
61-
want: true,
62-
},
63-
{
64-
name: "pending is no better than pending",
65-
args: args{
66-
css: CommitStatusPending,
67-
css2: CommitStatusPending,
68-
},
69-
want: true,
70-
},
71-
{
72-
name: "pending is no better than failure",
73-
args: args{
74-
css: CommitStatusPending,
75-
css2: CommitStatusFailure,
76-
},
77-
want: false,
78-
},
79-
{
80-
name: "pending is no better than error",
81-
args: args{
82-
css: CommitStatusPending,
83-
css2: CommitStatusError,
84-
},
85-
want: false,
86-
},
87-
{
88-
name: "failure is no better than success",
89-
args: args{
90-
css: CommitStatusFailure,
91-
css2: CommitStatusSuccess,
92-
},
93-
want: true,
94-
},
95-
{
96-
name: "failure is no better than pending",
97-
args: args{
98-
css: CommitStatusFailure,
99-
css2: CommitStatusPending,
100-
},
101-
want: true,
102-
},
103-
{
104-
name: "failure is no better than failure",
105-
args: args{
106-
css: CommitStatusFailure,
107-
css2: CommitStatusFailure,
108-
},
109-
want: true,
110-
},
111-
{
112-
name: "failure is no better than error",
113-
args: args{
114-
css: CommitStatusFailure,
115-
css2: CommitStatusError,
116-
},
117-
want: false,
118-
},
119-
{
120-
name: "error is no better than success",
121-
args: args{
122-
css: CommitStatusError,
123-
css2: CommitStatusSuccess,
124-
},
125-
want: true,
126-
},
127-
{
128-
name: "error is no better than pending",
129-
args: args{
130-
css: CommitStatusError,
131-
css2: CommitStatusPending,
132-
},
133-
want: true,
134-
},
135-
{
136-
name: "error is no better than failure",
137-
args: args{
138-
css: CommitStatusError,
139-
css2: CommitStatusFailure,
140-
},
141-
want: true,
142-
},
143-
{
144-
name: "error is no better than error",
145-
args: args{
146-
css: CommitStatusError,
147-
css2: CommitStatusError,
148-
},
149-
want: true,
150-
},
151-
{
152-
name: "unExpectedState is no better than success",
153-
args: args{
154-
css: unExpectedState,
155-
css2: CommitStatusSuccess,
156-
},
157-
want: false,
158-
},
159-
{
160-
name: "unExpectedState is no better than unExpectedState",
161-
args: args{
162-
css: unExpectedState,
163-
css2: unExpectedState,
164-
},
165-
want: false,
166-
},
17+
{CommitStatusError, CommitStatusFailure, true},
18+
{CommitStatusFailure, CommitStatusWarning, true},
19+
{CommitStatusWarning, CommitStatusPending, true},
20+
{CommitStatusPending, CommitStatusSuccess, true},
21+
{CommitStatusSuccess, CommitStatusSkipped, true},
22+
23+
{CommitStatusError, "unknown-xxx", false},
24+
{"unknown-xxx", CommitStatusFailure, true},
16725
}
16826
for _, tt := range tests {
169-
t.Run(tt.name, func(t *testing.T) {
170-
result := tt.args.css.NoBetterThan(tt.args.css2)
171-
assert.Equal(t, tt.want, result)
172-
})
27+
assert.Equal(t, tt.higher, tt.s1.HasHigherPriorityThan(tt.s2), "s1=%s, s2=%s, expected=%v", tt.s1, tt.s2, tt.higher)
17328
}
29+
assert.False(t, CommitStatusError.HasHigherPriorityThan(CommitStatusError))
17430
}

services/actions/commit_status.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,14 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
149149

150150
func toCommitStatus(status actions_model.Status) api.CommitStatusState {
151151
switch status {
152-
case actions_model.StatusSuccess, actions_model.StatusSkipped:
152+
case actions_model.StatusSuccess:
153153
return api.CommitStatusSuccess
154154
case actions_model.StatusFailure, actions_model.StatusCancelled:
155155
return api.CommitStatusFailure
156156
case actions_model.StatusWaiting, actions_model.StatusBlocked, actions_model.StatusRunning:
157157
return api.CommitStatusPending
158+
case actions_model.StatusSkipped:
159+
return api.CommitStatusSkipped
158160
default:
159161
return api.CommitStatusError
160162
}

services/convert/status.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ func ToCombinedStatus(ctx context.Context, statuses []*git_model.CommitStatus, r
4242
SHA: statuses[0].SHA,
4343
TotalCount: len(statuses),
4444
Repository: repo,
45-
URL: "",
45+
URL: "", // never set or used?
46+
State: api.CommitStatusSuccess,
4647
}
4748

4849
retStatus.Statuses = make([]*api.CommitStatus, 0, len(statuses))
4950
for _, status := range statuses {
5051
retStatus.Statuses = append(retStatus.Statuses, ToCommitStatus(ctx, status))
51-
if retStatus.State == "" || status.State.NoBetterThan(retStatus.State) {
52+
if status.State.HasHigherPriorityThan(retStatus.State) {
5253
retStatus.State = status.State
5354
}
5455
}
@@ -57,9 +58,13 @@ func ToCombinedStatus(ctx context.Context, statuses []*git_model.CommitStatus, r
5758
// > failure if any of the contexts report as error or failure
5859
// > pending if there are no statuses or a context is pending
5960
// > success if the latest status for all contexts is success
60-
if retStatus.State.IsError() {
61-
retStatus.State = api.CommitStatusFailure
61+
switch retStatus.State {
62+
case api.CommitStatusSkipped:
63+
retStatus.State = api.CommitStatusSuccess // all skipped means success
64+
case api.CommitStatusPending, api.CommitStatusSuccess:
65+
// use the current state for pending or success
66+
default:
67+
retStatus.State = api.CommitStatusFailure // otherwise, it is a failure
6268
}
63-
6469
return retStatus
6570
}

services/pull/commit_status.go

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -46,57 +46,31 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
4646

4747
// If required rule not match any action, then it is pending
4848
if targetStatus == "" {
49-
if structs.CommitStatusPending.NoBetterThan(returnedStatus) {
49+
if structs.CommitStatusPending.HasHigherPriorityThan(returnedStatus) {
5050
returnedStatus = structs.CommitStatusPending
5151
}
5252
break
5353
}
5454

55-
if targetStatus.NoBetterThan(returnedStatus) {
55+
if targetStatus.HasHigherPriorityThan(returnedStatus) {
5656
returnedStatus = targetStatus
5757
}
5858
}
5959
}
6060

6161
if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess {
62-
status := git_model.CalcCommitStatus(commitStatuses)
63-
if status != nil {
64-
return status.State
62+
if len(commitStatuses) == 0 {
63+
// "no statuses" should mean "pending"
64+
return structs.CommitStatusPending
6565
}
66-
return structs.CommitStatusSuccess
67-
}
68-
69-
return returnedStatus
70-
}
71-
72-
// IsCommitStatusContextSuccess returns true if all required status check contexts succeed.
73-
func IsCommitStatusContextSuccess(commitStatuses []*git_model.CommitStatus, requiredContexts []string) bool {
74-
// If no specific context is required, require that last commit status is a success
75-
if len(requiredContexts) == 0 {
7666
status := git_model.CalcCommitStatus(commitStatuses)
77-
if status == nil || status.State != structs.CommitStatusSuccess {
78-
return false
67+
if status.State == structs.CommitStatusSkipped {
68+
return structs.CommitStatusSuccess // if all statuses are skipped, return success
7969
}
80-
return true
70+
return status.State
8171
}
8272

83-
for _, ctx := range requiredContexts {
84-
var found bool
85-
for _, commitStatus := range commitStatuses {
86-
if commitStatus.Context == ctx {
87-
if commitStatus.State != structs.CommitStatusSuccess {
88-
return false
89-
}
90-
91-
found = true
92-
break
93-
}
94-
}
95-
if !found {
96-
return false
97-
}
98-
}
99-
return true
73+
return returnedStatus
10074
}
10175

10276
// IsPullCommitStatusPass returns if all required status checks PASS

0 commit comments

Comments
 (0)