Compare commits

...

1 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
750a9079fb 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>
2026-04-23 07:45:09 +00:00
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
}