mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-09 17:37:20 -06:00
enhance(actions): create commit status for workflow_dispatch runs (#39961)
Runs triggered by `workflow_dispatch` never created a commit status, so the commit showed no sign that a workflow ran on it. - Post the status on the run's commit, e.g. `ci / e2e (workflow_dispatch)`. `schedule` runs stay unchanged. - These statuses don't count for pull requests. They don't show in a PR's checks and can't satisfy or fail its required checks, even through broad globs like `ci / e2e*`. Closes https://github.com/go-gitea/gitea/issues/39639 --------- Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -165,11 +165,12 @@ func ScopedWorkflowStatusContextName(prefix, displayName, jobName, event string)
|
||||
return strings.TrimSpace(fmt.Sprintf("%s: %s", prefix, WorkflowStatusContextName(displayName, jobName, event)))
|
||||
}
|
||||
|
||||
// ShouldEventCreateCommitStatus reports whether a run triggered by the given workflow `on:` event posts a commit status,
|
||||
// IsPullRequestCheckEvent reports whether a run triggered by the given workflow `on:` event posts a commit status that pull requests count,
|
||||
// so its context can serve as a required status check.
|
||||
// TODO: this allowlist duplicates the truth in services/actions.getCommitStatusEventNameAndCommitID, which decides the actual event string and whether a status is posted.
|
||||
// The two are kept in sync by hand and can drift; unify them into a single source so adding a status-producing event in one place automatically updates the other.
|
||||
func ShouldEventCreateCommitStatus(event string) bool {
|
||||
// TODO: this allowlist duplicates the truth in services/actions.getCommitStatusEventNameAndCommitID, which decides the actual event string and whether a status is posted,
|
||||
// and in services/pull.GetLatestPullCommitStatuses, which decides whether pull requests count it.
|
||||
// They are kept in sync by hand and can drift, unify them into a single source so adding a status-producing event in one place automatically updates the others.
|
||||
func IsPullRequestCheckEvent(event string) bool {
|
||||
switch event {
|
||||
case "push", "pull_request", "pull_request_target", "pull_request_review", "pull_request_review_comment", "release":
|
||||
return true
|
||||
|
||||
@@ -37,9 +37,10 @@ const (
|
||||
// FIXME: This event should be a group of pull_request_review_xxx events
|
||||
HookEventPullRequestReview HookEventType = "pull_request_review"
|
||||
// Actions event only
|
||||
HookEventSchedule HookEventType = "schedule"
|
||||
HookEventWorkflowRun HookEventType = "workflow_run"
|
||||
HookEventWorkflowJob HookEventType = "workflow_job"
|
||||
HookEventSchedule HookEventType = "schedule"
|
||||
HookEventWorkflowDispatch HookEventType = "workflow_dispatch"
|
||||
HookEventWorkflowRun HookEventType = "workflow_run"
|
||||
HookEventWorkflowJob HookEventType = "workflow_job"
|
||||
)
|
||||
|
||||
func AllEvents() []HookEventType {
|
||||
|
||||
@@ -420,9 +420,9 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxStatusCheckData(ctx *context.C
|
||||
statusCheckData := &pullCommitStatusCheckData{}
|
||||
data.StatusCheckData = statusCheckData
|
||||
|
||||
commitStatuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, prInfo.CompareInfo.HeadCommitID, db.ListOptionsAll)
|
||||
commitStatuses, err := pull_service.GetLatestPullCommitStatuses(ctx, ctx.Repo.Repository.ID, prInfo.CompareInfo.HeadCommitID)
|
||||
if err != nil {
|
||||
log.Error("GetLatestCommitStatus: %v", err)
|
||||
log.Error("GetLatestPullCommitStatuses: %v", err)
|
||||
}
|
||||
|
||||
// Effective required contexts = branch-protection contexts + required scoped workflow checks.
|
||||
|
||||
@@ -158,8 +158,7 @@ func deriveScopedStatusContexts(prefix, displayName string, content []byte, even
|
||||
}
|
||||
eventNames := make([]string, 0, len(events))
|
||||
for _, e := range events {
|
||||
// only events whose runs post a commit status can be a required check; workflow_dispatch, schedule, etc. post none.
|
||||
if actions_module.ShouldEventCreateCommitStatus(e.Name) {
|
||||
if actions_module.IsPullRequestCheckEvent(e.Name) {
|
||||
eventNames = append(eventNames, e.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
}, got)
|
||||
})
|
||||
|
||||
t.Run("only status-producing events; workflow_dispatch/schedule/workflow_call skipped", func(t *testing.T) {
|
||||
t.Run("only pull request check events; workflow_dispatch/schedule/workflow_call skipped", func(t *testing.T) {
|
||||
content := []byte(`name: CI
|
||||
on:
|
||||
push:
|
||||
@@ -65,21 +65,6 @@ jobs:
|
||||
events, err := actions_module.GetEventsFromContent(content)
|
||||
require.NoError(t, err)
|
||||
got := deriveScopedStatusContexts("org/src", "CI", content, events)
|
||||
assert.Equal(t, []string{"org/src: CI / j (push)"}, got) // only push posts a commit status
|
||||
})
|
||||
|
||||
t.Run("a workflow_dispatch-only workflow has no expected contexts", func(t *testing.T) {
|
||||
content := []byte(`name: Deploy
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo
|
||||
`)
|
||||
events, err := actions_module.GetEventsFromContent(content)
|
||||
require.NoError(t, err)
|
||||
got := deriveScopedStatusContexts("org/src", "Deploy", content, events)
|
||||
assert.Empty(t, got) // workflow_dispatch posts no commit status -> nothing to preview (and it cannot be a required check)
|
||||
assert.Equal(t, []string{"org/src: CI / j (push)"}, got)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func getCommitStatusEventNameAndCommitID(run *actions_model.ActionRun) (event, c
|
||||
webhook_module.HookEventPullRequestReviewComment:
|
||||
event = run.TriggerEvent
|
||||
commitID = run.CommitSHA
|
||||
case webhook_module.HookEventRelease:
|
||||
case webhook_module.HookEventRelease, webhook_module.HookEventWorkflowDispatch:
|
||||
event = string(run.Event)
|
||||
commitID = run.CommitSHA
|
||||
default: // do nothing, return empty
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"gitea.dev/modules/actions/jobparser"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
webhook_module "gitea.dev/modules/webhook"
|
||||
|
||||
"go.yaml.in/yaml/v4"
|
||||
)
|
||||
@@ -29,7 +30,7 @@ func EvaluateRunConcurrencyFillModel(ctx context.Context, run *actions_model.Act
|
||||
actionsRunCtx := GenerateGiteaContext(ctx, run, attempt, nil)
|
||||
jobResults := map[string]*jobparser.JobResult{"": {}}
|
||||
if inputs == nil {
|
||||
if run.Event == "workflow_dispatch" {
|
||||
if run.Event == webhook_module.HookEventWorkflowDispatch {
|
||||
setting.PanicInDevOrTesting("run %d: workflow_dispatch inputs must be resolved by the caller", run.ID)
|
||||
}
|
||||
inputs = map[string]any{}
|
||||
|
||||
@@ -17,11 +17,12 @@ import (
|
||||
"gitea.dev/modules/log"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
webhook_module "gitea.dev/modules/webhook"
|
||||
)
|
||||
|
||||
// dispatchInputsForJob types a top-level job's `inputs.*` from EventPayload, empty for other events.
|
||||
func dispatchInputsForJob(run *actions_model.ActionRun, job *actions_model.ActionRunJob) (map[string]any, error) {
|
||||
if run.Event != "workflow_dispatch" {
|
||||
if run.Event != webhook_module.HookEventWorkflowDispatch {
|
||||
return map[string]any{}, nil
|
||||
}
|
||||
var payload api.WorkflowDispatchPayload
|
||||
@@ -85,7 +86,7 @@ func decodeWorkflowCallInputs(caller *actions_model.ActionRunJob) (map[string]an
|
||||
// calledWorkflowInputs overlays the run's dispatch inputs with `workflowCallInputs`, as GitHub does.
|
||||
func calledWorkflowInputs(ctx context.Context, run *actions_model.ActionRun, caller *actions_model.ActionRunJob, workflowCallInputs map[string]any) (map[string]any, error) {
|
||||
top := caller
|
||||
for run.Event == "workflow_dispatch" && top.ParentJobID != 0 {
|
||||
for run.Event == webhook_module.HookEventWorkflowDispatch && top.ParentJobID != 0 {
|
||||
parent, err := actions_model.GetRunJobByRunAndID(ctx, run.ID, top.ParentJobID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load caller job %d: %w", top.ParentJobID, err)
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"gitea.dev/modules/reqctx"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
webhook_module "gitea.dev/modules/webhook"
|
||||
"gitea.dev/services/audit"
|
||||
gitea_context "gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
@@ -129,7 +130,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
|
||||
Ref: string(refName),
|
||||
CommitSHA: runTargetCommit.ID.String(),
|
||||
IsForkPullRequest: false,
|
||||
Event: "workflow_dispatch",
|
||||
Event: webhook_module.HookEventWorkflowDispatch,
|
||||
TriggerEvent: "workflow_dispatch",
|
||||
Status: actions_model.StatusWaiting,
|
||||
// local dispatch: own repo at the target commit; the scoped path overrides these below
|
||||
|
||||
@@ -9,12 +9,14 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/commitstatus"
|
||||
"gitea.dev/modules/container"
|
||||
"gitea.dev/modules/git"
|
||||
@@ -139,9 +141,9 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
|
||||
return "", fmt.Errorf("LoadBaseRepo: %w", err)
|
||||
}
|
||||
|
||||
commitStatuses, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
|
||||
commitStatuses, err := GetLatestPullCommitStatuses(ctx, pr.BaseRepo.ID, sha)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("GetLatestCommitStatus: %w", err)
|
||||
return "", fmt.Errorf("GetLatestPullCommitStatuses: %w", err)
|
||||
}
|
||||
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
@@ -156,6 +158,14 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
|
||||
return MergeRequiredContextsCommitStatus(commitStatuses, requiredContexts), nil
|
||||
}
|
||||
|
||||
// GetLatestPullCommitStatuses returns a pull request head commit's latest statuses without workflow_dispatch runs', which don't count for pull requests
|
||||
func GetLatestPullCommitStatuses(ctx context.Context, repoID int64, sha string) ([]*git_model.CommitStatus, error) {
|
||||
statuses, err := git_model.GetLatestCommitStatus(ctx, repoID, sha, db.ListOptionsAll)
|
||||
return slices.DeleteFunc(statuses, func(status *git_model.CommitStatus) bool {
|
||||
return status.CreatorID == user_model.ActionsUserID && strings.HasSuffix(status.Context, " (workflow_dispatch)")
|
||||
}), err
|
||||
}
|
||||
|
||||
// EffectiveRequiredContexts returns the required status-check contexts to enforce, drawn from:
|
||||
// 1. every required scoped workflow's status-check patterns effective for the repo (always)
|
||||
// 2. each given protected branch rule's own configured contexts, only when that rule's status check is enabled
|
||||
|
||||
@@ -937,7 +937,7 @@ func getAllCommitStatus(ctx context.Context, doer *user_model.User, gitRepo *git
|
||||
return nil, nil, shaErr
|
||||
}
|
||||
|
||||
statuses, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
|
||||
statuses, err = GetLatestPullCommitStatuses(ctx, pr.BaseRepo.ID, sha)
|
||||
for _, status := range statuses {
|
||||
status.Repo = pr.BaseRepo // the repo is already loaded, spare the permission lookup a query
|
||||
}
|
||||
|
||||
@@ -11,11 +11,15 @@ import (
|
||||
|
||||
actions_model "gitea.dev/models/actions"
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
pull_service "gitea.dev/services/pull"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWorkflowWithInputsContext(t *testing.T) {
|
||||
@@ -73,6 +77,15 @@ jobs:
|
||||
task := wRunner.fetchTask(t)
|
||||
_, _, run := getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "Build APP on windows", run.Title)
|
||||
|
||||
statuses, err := git_model.GetLatestCommitStatus(t.Context(), repo.ID, run.CommitSHA, db.ListOptionsAll)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, statuses, 1)
|
||||
assert.Equal(t, "Test Inputs Context / build (workflow_dispatch)", statuses[0].Context)
|
||||
|
||||
pullStatuses, err := pull_service.GetLatestPullCommitStatuses(t.Context(), repo.ID, run.CommitSHA)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, pullStatuses)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user