fix(actions): evaluate job-level if: before concurrency check (#39437)

Gitea doesn't evaluate a job's `if:` before checking the job's
concurrency group, which causes a job that should have been skipped to
incorrectly cancel other jobs in the same concurrency group.

This PR makes Gitea decide `if:` for every job before it becomes
waiting, including jobs without `needs` at insertion, on approval and on
rerun. A skipped job therefore no longer takes part in job concurrency
or holds a max-parallel slot, and a reusable caller whose `if:` is false
is no longer expanded on approval or rerun. An invalid `if:` skips the
job with an error summary.

After this PR, Gitea decides all jobs' `if:` expressions and sends `if:
always()` to the runner, so the runner no longer needs to evaluate a
job's `if:` again ([gitea/runner
`run_context.go`](https://gitea.com/gitea/runner/src/commit/81add274599355ec1838b6ebe45804890d40bab9/act/runner/run_context.go#L1195)).

---------

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
Zettat123
2026-09-26 01:01:21 -06:00
committed by GitHub
parent f0f53a76ee
commit a15f032026
12 changed files with 263 additions and 83 deletions
+52
View File
@@ -7,8 +7,11 @@ import (
"testing"
actions_model "gitea.dev/models/actions"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/container"
"gitea.dev/modules/test"
"gitea.dev/modules/util"
"github.com/stretchr/testify/assert"
@@ -453,3 +456,52 @@ func TestCollectMatrixCollapse(t *testing.T) {
assert.Empty(t, plan.matrixSiblingSkipTemplateIDs)
})
}
func TestRerunDecidesJobIf(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
defer test.MockVariableValue(&EmitJobsIfReadyByRun, func(int64) error { return nil })()
ctx := t.Context()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
variable, err := actions_model.InsertVariable(ctx, 0, repo.ID, "DEPLOY", "yes", "")
require.NoError(t, err)
run := insertMaxParallelRun(t, `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo
deploy:
if: vars.DEPLOY == 'yes'
runs-on: ubuntu-latest
steps:
- run: echo
`, false)
jobs := map[string]*actions_model.ActionRunJob{}
for _, job := range runJobs(t, run.ID, run.LatestAttemptID) {
require.Equal(t, actions_model.StatusWaiting, job.Status)
job.Status = actions_model.StatusSuccess
_, err = actions_model.UpdateRunJob(ctx, job, nil, "status")
require.NoError(t, err)
jobs[job.JobID] = job
}
run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run.ID})
variable.Data = "no"
_, err = actions_model.UpdateVariableCols(ctx, variable, "data")
require.NoError(t, err)
attempt, err := RerunWorkflowRunJobs(ctx, repo, run, &user_model.User{ID: 1}, []*actions_model.ActionRunJob{jobs["deploy"]})
require.NoError(t, err)
rerunJobs := map[string]*actions_model.ActionRunJob{}
for _, job := range runJobs(t, run.ID, attempt.ID) {
rerunJobs[job.JobID] = job
}
assert.Equal(t, actions_model.StatusSuccess, rerunJobs["build"].Status)
assert.Equal(t, actions_model.StatusSkipped, rerunJobs["deploy"].Status)
attempt = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunAttempt{ID: attempt.ID})
assert.Equal(t, actions_model.StatusSuccess, attempt.Status)
assert.NotZero(t, attempt.Stopped)
assert.Equal(t, actions_model.StatusSuccess, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run.ID}).Status)
}