fix: commit status reporting (backport #37372)

Backport of 8e85454a50

Fixes the issue that status report always shows "waiting to run" when
already running, by comparing state, targetURL, and description (not
just state) in the deduplication check.

Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (claude-sonnet-4-5) <noreply@anthropic.com>

Agent-Logs-Url: https://github.com/go-gitea/gitea/sessions/1b315e9f-698c-4564-9ffa-aac1b9cb1692

Co-authored-by: silverwind <115237+silverwind@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-23 07:45:09 +00:00
committed by GitHub
parent 0280455356
commit 750a9079fb
2 changed files with 125 additions and 36 deletions

View File

@@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"path"
"strconv"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
@@ -143,57 +142,59 @@ func createCommitStatus(ctx context.Context, repo *repo_model.Repository, event,
if wfs, err := jobparser.Parse(job.WorkflowPayload); err == nil && len(wfs) > 0 {
runName = wfs[0].Name
}
ctxName := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
ctxName = strings.TrimSpace(ctxName) // git_model.NewCommitStatus also trims spaces
ctxName := strings.TrimSpace(fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)) // git_model.NewCommitStatus also trims spaces
state := toCommitStatus(job.Status)
if statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commitID, db.ListOptionsAll); err == nil {
for _, v := range statuses {
if v.Context == ctxName {
if v.State == state {
// no need to update
return nil
}
break
}
}
} else {
targetURL := fmt.Sprintf("%s/jobs/%d", run.Link(), job.ID)
description := toCommitStatusDescription(job)
statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commitID, db.ListOptionsAll)
if err != nil {
return fmt.Errorf("GetLatestCommitStatus: %w", err)
}
var description string
switch job.Status {
// TODO: if we want support description in different languages, we need to support i18n placeholders in it
case actions_model.StatusSuccess:
description = fmt.Sprintf("Successful in %s", job.Duration())
case actions_model.StatusFailure:
description = fmt.Sprintf("Failing after %s", job.Duration())
case actions_model.StatusCancelled:
description = "Has been cancelled"
case actions_model.StatusSkipped:
description = "Has been skipped"
case actions_model.StatusRunning:
description = "Has started running"
case actions_model.StatusWaiting:
description = "Waiting to run"
case actions_model.StatusBlocked:
description = "Blocked by required conditions"
default:
description = "Unknown status: " + strconv.Itoa(int(job.Status))
for _, v := range statuses {
if v.Context == ctxName {
if v.State == state && v.TargetURL == targetURL && v.Description == description {
return nil
}
break
}
}
creator := user_model.NewActionsUser()
status := git_model.CommitStatus{
SHA: commitID,
TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), job.ID),
TargetURL: targetURL,
Description: description,
Context: ctxName,
CreatorID: creator.ID,
State: state,
CreatorID: creator.ID,
}
return commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID, &status)
}
func toCommitStatusDescription(job *actions_model.ActionRunJob) string {
switch job.Status {
// TODO: if we want support description in different languages, we need to support i18n placeholders in it
case actions_model.StatusSuccess:
return fmt.Sprintf("Successful in %s", job.Duration())
case actions_model.StatusFailure:
return fmt.Sprintf("Failing after %s", job.Duration())
case actions_model.StatusCancelled:
return "Has been cancelled"
case actions_model.StatusSkipped:
return "Has been skipped"
case actions_model.StatusRunning:
return "Has started running"
case actions_model.StatusWaiting:
return "Waiting to run"
case actions_model.StatusBlocked:
return "Blocked by required conditions"
default:
return fmt.Sprintf("Unknown status: %d", job.Status)
}
}
func toCommitStatus(status actions_model.Status) commitstatus.CommitStatusState {
switch status {
case actions_model.StatusSuccess:

View File

@@ -0,0 +1,88 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/gitrepo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateCommitStatus_Dedupe(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
gitRepo, err := gitrepo.OpenRepository(t.Context(), repo)
require.NoError(t, err)
defer gitRepo.Close()
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
require.NoError(t, err)
run := &actions_model.ActionRun{
ID: 99001,
RepoID: repo.ID,
Repo: repo,
WorkflowID: "status-dedupe-test.yaml",
}
job := &actions_model.ActionRunJob{
ID: 99002,
RunID: run.ID,
RepoID: repo.ID,
Name: "status-dedupe-job",
Status: actions_model.StatusWaiting,
}
expectedContext := "status-dedupe-test.yaml / status-dedupe-job (push)"
expectedTargetURL := run.Link() + "/jobs/99002"
require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), run, job))
statuses := findCommitStatusesForContext(t, repo.ID, commit.ID.String(), expectedContext)
require.Len(t, statuses, 1)
assert.Equal(t, commitstatus.CommitStatusPending, statuses[0].State)
assert.Equal(t, "Waiting to run", statuses[0].Description)
assert.Equal(t, expectedTargetURL, statuses[0].TargetURL)
job.Status = actions_model.StatusRunning
require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), run, job))
statuses = findCommitStatusesForContext(t, repo.ID, commit.ID.String(), expectedContext)
require.Len(t, statuses, 2)
assert.Equal(t, "Waiting to run", statuses[0].Description)
assert.Equal(t, commitstatus.CommitStatusPending, statuses[1].State)
assert.Equal(t, "Has started running", statuses[1].Description)
assert.Equal(t, expectedTargetURL, statuses[1].TargetURL)
require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), run, job))
statuses = findCommitStatusesForContext(t, repo.ID, commit.ID.String(), expectedContext)
assert.Len(t, statuses, 2)
job.Status = actions_model.StatusSuccess
require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), run, job))
statuses = findCommitStatusesForContext(t, repo.ID, commit.ID.String(), expectedContext)
require.Len(t, statuses, 3)
assert.Equal(t, commitstatus.CommitStatusSuccess, statuses[2].State)
}
func findCommitStatusesForContext(t *testing.T, repoID int64, sha, context string) []*git_model.CommitStatus {
t.Helper()
var statuses []*git_model.CommitStatus
err := db.GetEngine(t.Context()).
Where("repo_id = ? AND sha = ? AND context = ?", repoID, sha, context).
Asc("`index`").
Find(&statuses)
require.NoError(t, err)
return statuses
}