Compare commits

...

5 Commits

Author SHA1 Message Date
Zettat123
f301a8aa1a Improve actions notifier for workflow_run (#37088) (#37099)
Backport #37088

Changes:

- Make `GetActionWorkflow` only convert the target workflow
- In `getActionWorkflowEntry`, use `branchName` instead of resolving the
default branch name from `commit.GetBranchName()`
- Add `ref` to `workflow_run` notify input to avoid the empty `ref`
warning
2026-04-03 15:42:39 -07:00
Giteabot
b0cc5ccd17 Update Combine method to treat warnings as failures and adjust tests (#37048) (#37075)
Backport #37048 by @bircni

Treat Commit Status Warnings as errors

> The root problem is that the definition of "warning" are different
across systems.
> 
> * Sometimes, "warning" is treated as "acceptable" (Gitea 1.25)
> * Sometimes, "warning" is mapped from "Result.UNSTABLE", which means
"there are test failures" and it is "failure" in Gitea
> 
> **To avoid breaking existing users, the best choice is to revert the
behavior on Gitea side: treat "warning" as "error".**

https://github.com/go-gitea/gitea/issues/37042#issuecomment-4158231611

fixes https://github.com/go-gitea/gitea/issues/37042

Signed-off-by: Nicolas <bircni@icloud.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-04-01 13:20:13 -07:00
Zettat123
159f74040c Fix missing workflow_run notifications when updating jobs from multiple runs (#36997) (#37003)
Backport #36997

This PR fixes `notifyWorkflowJobStatusUpdate` to send
`WorkflowRunStatusUpdate` for each affected workflow run instead of only
the first run in the input job list.
2026-03-26 20:08:01 -07:00
Giteabot
af29b8182c Catch scanner error when possible to avoid bypass (#36963) (#36976)
Backport #36963 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2026-03-24 17:18:01 +00:00
Giteabot
9d6817f9c0 Fix user settings sidebar showing disabled features on some pages (#36958) (#36969)
Backport #36958 by @bircni

Move UserDisabledFeatures context data into a shared SettingsCtxData
middleware for the /user/settings route group, so it is set consistently
on all pages (including Notifications, Actions, etc.) instead of only on
the handlers that remembered to set it individually.

Fixes #36954

Co-authored-by: Nicolas <bircni@icloud.com>
2026-03-24 01:25:39 +01:00
22 changed files with 157 additions and 82 deletions

View File

@@ -276,6 +276,9 @@ Gitea or set your environment appropriately.`, "")
lastline = 0
}
}
if err := scanner.Err(); err != nil {
return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err)
}
if count > 0 {
hookOptions.OldCommitIDs = oldCommitIDs[:count]
@@ -415,6 +418,11 @@ Gitea or set your environment appropriately.`, "")
count = 0
}
}
if err := scanner.Err(); err != nil {
_ = dWriter.Close()
hookPrintResults(results)
return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err)
}
if count == 0 {
if wasEmpty && masterPushed {

View File

@@ -139,7 +139,7 @@ func Test_CalcCommitStatus(t *testing.T) {
},
},
expected: &git_model.CommitStatus{
State: commitstatus.CommitStatusPending,
State: commitstatus.CommitStatusFailure,
},
},
{

View File

@@ -59,6 +59,10 @@ func IsDefaultBranchWorkflow(triggedEvent webhook_module.HookEventType) bool {
// Github "issues" event
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues
return true
case webhook_module.HookEventWorkflowRun:
// GitHub "workflow_run" event
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run
return true
}
return false

View File

@@ -61,16 +61,17 @@ type CommitStatusStates []CommitStatusState //nolint:revive // export stutter
// According to https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference
// > Additionally, a combined state is returned. The state is one of:
// > failure if any of the contexts report as error or failure
// > failure if any of the contexts report as warning (Gitea specific behavior)
// > pending if there are no statuses or a context is pending
// > success if the latest status for all contexts is success
func (css CommitStatusStates) Combine() CommitStatusState {
successCnt := 0
for _, state := range css {
switch {
case state.IsError() || state.IsFailure():
case state.IsError() || state.IsFailure() || state.IsWarning():
return CommitStatusFailure
case state.IsPending():
case state.IsSuccess() || state.IsWarning() || state.IsSkipped():
case state.IsSuccess() || state.IsSkipped():
successCnt++
}
}

View File

@@ -41,7 +41,7 @@ func TestCombine(t *testing.T) {
{
name: "warning",
states: CommitStatusStates{CommitStatusWarning},
expected: CommitStatusSuccess,
expected: CommitStatusFailure,
},
// 2 states
{
@@ -62,7 +62,7 @@ func TestCombine(t *testing.T) {
{
name: "pending and warning",
states: CommitStatusStates{CommitStatusPending, CommitStatusWarning},
expected: CommitStatusPending,
expected: CommitStatusFailure,
},
{
name: "success and error",
@@ -77,7 +77,7 @@ func TestCombine(t *testing.T) {
{
name: "success and warning",
states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning},
expected: CommitStatusSuccess,
expected: CommitStatusFailure,
},
{
name: "error and failure",
@@ -98,7 +98,7 @@ func TestCombine(t *testing.T) {
{
name: "pending, success and warning",
states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusWarning},
expected: CommitStatusPending,
expected: CommitStatusFailure,
},
{
name: "pending, success and error",
@@ -133,7 +133,7 @@ func TestCombine(t *testing.T) {
{
name: "success, warning and skipped",
states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning, CommitStatusSkipped},
expected: CommitStatusSuccess,
expected: CommitStatusFailure,
},
// All success
{
@@ -181,12 +181,12 @@ func TestCombine(t *testing.T) {
{
name: "mixed states with all success",
states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusPending, CommitStatusWarning},
expected: CommitStatusPending,
expected: CommitStatusFailure,
},
{
name: "all success with warning",
states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusSuccess, CommitStatusWarning},
expected: CommitStatusSuccess,
expected: CommitStatusFailure,
},
}

View File

@@ -347,27 +347,6 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
return string(bytes), nil
}
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
func (c *Commit) GetBranchName() (string, error) {
cmd := gitcmd.NewCommand("name-rev")
if DefaultFeatures().CheckVersionAtLeast("2.13.0") {
cmd.AddArguments("--exclude", "refs/tags/*")
}
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
data, _, err := cmd.RunStdString(c.repo.Ctx, &gitcmd.RunOpts{Dir: c.repo.Path})
if err != nil {
// handle special case where git can not describe commit
if strings.Contains(err.Error(), "cannot describe") {
return "", nil
}
return "", err
}
// name-rev commitID output will be "master" or "master~12"
return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil
}
// CommitFileStatus represents status of files in a commit.
type CommitFileStatus struct {
Added []string

View File

@@ -7,7 +7,6 @@ import (
"errors"
"net/http"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
shared "code.gitea.io/gitea/routers/web/shared/secrets"
@@ -74,7 +73,6 @@ func Secrets(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("actions.actions")
ctx.Data["PageType"] = "secrets"
ctx.Data["PageIsSharedSettingsSecrets"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
sCtx, err := getSecretsCtx(ctx)
if err != nil {

View File

@@ -321,7 +321,6 @@ func loadAccountData(ctx *context.Context) {
ctx.Data["Emails"] = emails
ctx.Data["ActivationsPending"] = pendingActivation
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
if setting.Service.UserDeleteWithCommentsMaxTime != 0 {
ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()

View File

@@ -10,7 +10,6 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
@@ -27,7 +26,6 @@ const (
func Applications(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings.applications")
ctx.Data["PageIsSettingsApplications"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
loadApplicationsData(ctx)
@@ -39,7 +37,6 @@ func ApplicationsPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewAccessTokenForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsApplications"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
_ = ctx.Req.ParseForm()
var scopeNames []string

View File

@@ -6,7 +6,6 @@ package setting
import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
@@ -20,7 +19,6 @@ const (
func BlockedUsers(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("user.block.list")
ctx.Data["PageIsSettingsBlockedUsers"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
shared_user.BlockedUsers(ctx, ctx.Doer)
if ctx.Written() {

View File

@@ -35,7 +35,6 @@ func Keys(ctx *context.Context) {
ctx.Data["DisableSSH"] = setting.SSH.Disabled
ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer
ctx.Data["AllowPrincipals"] = setting.SSH.AuthorizedPrincipalsEnabled
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
loadKeysData(ctx)
@@ -50,7 +49,6 @@ func KeysPost(ctx *context.Context) {
ctx.Data["DisableSSH"] = setting.SSH.Disabled
ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer
ctx.Data["AllowPrincipals"] = setting.SSH.AuthorizedPrincipalsEnabled
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
if ctx.HasError() {
loadKeysData(ctx)
@@ -341,5 +339,4 @@ func loadKeysData(ctx *context.Context) {
ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg")
ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh")
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
}

View File

@@ -25,7 +25,6 @@ const (
func Packages(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
shared.SetPackagesContext(ctx, ctx.Doer)
@@ -35,7 +34,6 @@ func Packages(ctx *context.Context) {
func PackagesRuleAdd(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
shared.SetRuleAddContext(ctx)
@@ -45,7 +43,6 @@ func PackagesRuleAdd(ctx *context.Context) {
func PackagesRuleEdit(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
shared.SetRuleEditContext(ctx, ctx.Doer)
@@ -55,7 +52,6 @@ func PackagesRuleEdit(ctx *context.Context) {
func PackagesRuleAddPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsPackages"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
shared.PerformRuleAddPost(
ctx,
@@ -68,7 +64,6 @@ func PackagesRuleAddPost(ctx *context.Context) {
func PackagesRuleEditPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
shared.PerformRuleEditPost(
ctx,
@@ -81,7 +76,6 @@ func PackagesRuleEditPost(ctx *context.Context) {
func PackagesRulePreview(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
shared.SetRulePreviewContext(ctx, ctx.Doer)

View File

@@ -49,8 +49,6 @@ func Profile(ctx *context.Context) {
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx)
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
ctx.HTML(http.StatusOK, tplSettingsProfile)
}
@@ -60,7 +58,6 @@ func ProfilePost(ctx *context.Context) {
ctx.Data["PageIsSettingsProfile"] = true
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx)
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplSettingsProfile)
@@ -200,7 +197,6 @@ func DeleteAvatar(ctx *context.Context) {
func Organization(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings.organization")
ctx.Data["PageIsSettingsOrganization"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
opts := organization.FindOrgOptions{
ListOptions: db.ListOptions{
@@ -232,7 +228,6 @@ func Organization(ctx *context.Context) {
func Repos(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings.repos")
ctx.Data["PageIsSettingsRepos"] = true
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
ctx.Data["allowAdopt"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories
ctx.Data["allowDelete"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories
@@ -340,7 +335,6 @@ func Appearance(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings.appearance")
ctx.Data["PageIsSettingsAppearance"] = true
ctx.Data["AllThemes"] = webtheme.GetAvailableThemes()
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
var hiddenCommentTypes *big.Int
val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes)

View File

@@ -156,5 +156,4 @@ func loadSecurityData(ctx *context.Context) {
return
}
ctx.Data["OpenIDs"] = openid
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
}

View File

@@ -9,9 +9,17 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
)
func SettingsCtxData(ctx *context.Context) {
ctx.Data["PageIsUserSettings"] = true
ctx.Data["EnablePackages"] = setting.Packages.Enabled
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
}
func UpdatePreferences(ctx *context.Context) {
type preferencesForm struct {
CodeViewShowFileTree bool `json:"codeViewShowFileTree"`

View File

@@ -7,7 +7,6 @@ import (
"net/http"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
@@ -25,7 +24,6 @@ func Webhooks(ctx *context.Context) {
ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks"
ctx.Data["BaseLinkNew"] = setting.AppSubURL + "/user/settings/hooks"
ctx.Data["Description"] = ctx.Tr("settings.hooks.desc")
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID})
if err != nil {

View File

@@ -682,7 +682,7 @@ func registerWebRoutes(m *web.Router) {
m.Get("", user_setting.BlockedUsers)
m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost)
})
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled, "EnableNotifyMail", setting.Service.EnableNotifyMail))
}, reqSignIn, user_setting.SettingsCtxData)
m.Group("/user", func() {
m.Get("/activate", auth.Activate)

View File

@@ -36,14 +36,27 @@ func StopEndlessTasks(ctx context.Context) error {
}
func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.ActionRunJob) {
if len(jobs) > 0 {
CreateCommitStatus(ctx, jobs...)
for _, job := range jobs {
_ = job.LoadAttributes(ctx)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
if len(jobs) == 0 {
return
}
CreateCommitStatus(ctx, jobs...)
runs := make(map[int64]*actions_model.ActionRun, len(jobs))
for _, job := range jobs {
if err := job.LoadAttributes(ctx); err != nil {
log.Error("Failed to load job attributes: %v", err)
continue
}
job := jobs[0]
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
if _, ok := runs[job.RunID]; !ok {
runs[job.RunID] = job.Run
}
}
for _, run := range runs {
notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
}
}

View File

@@ -816,12 +816,14 @@ func (n *actionsNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *rep
return
}
newNotifyInput(repo, sender, webhook_module.HookEventWorkflowRun).WithPayload(&api.WorkflowRunPayload{
Action: status,
Workflow: convertedWorkflow,
WorkflowRun: convertedRun,
Organization: org,
Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm_model.AccessModeOwner}),
Sender: convert.ToUser(ctx, sender, nil),
}).Notify(ctx)
newNotifyInput(repo, sender, webhook_module.HookEventWorkflowRun).
WithRef(git.RefNameFromBranch(repo.DefaultBranch).String()).
WithPayload(&api.WorkflowRunPayload{
Action: status,
Workflow: convertedWorkflow,
WorkflowRun: convertedRun,
Organization: org,
Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm_model.AccessModeOwner}),
Sender: convert.ToUser(ctx, sender, nil),
}).Notify(ctx)
}

View File

@@ -390,14 +390,12 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
}, nil
}
func getActionWorkflowEntry(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
func getActionWorkflowEntry(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, branchName, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions)
cfg := cfgUnit.ActionsConfig()
defaultBranch, _ := commit.GetBranchName()
workflowURL := fmt.Sprintf("%s/actions/workflows/%s", repo.APIURL(), util.PathEscapeSegments(entry.Name()))
workflowRepoURL := fmt.Sprintf("%s/src/branch/%s/%s/%s", repo.HTMLURL(ctx), util.PathEscapeSegments(defaultBranch), util.PathEscapeSegments(folder), util.PathEscapeSegments(entry.Name()))
workflowRepoURL := fmt.Sprintf("%s/src/branch/%s/%s/%s", repo.HTMLURL(ctx), util.PathEscapeSegments(branchName), util.PathEscapeSegments(folder), util.PathEscapeSegments(entry.Name()))
badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", repo.HTMLURL(ctx), util.PathEscapeSegments(entry.Name()), url.QueryEscape(repo.DefaultBranch))
// See https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#get-a-workflow
@@ -462,21 +460,26 @@ func ListActionWorkflows(ctx context.Context, gitrepo *git.Repository, repo *rep
workflows := make([]*api.ActionWorkflow, len(entries))
for i, entry := range entries {
workflows[i] = getActionWorkflowEntry(ctx, repo, defaultBranchCommit, folder, entry)
workflows[i] = getActionWorkflowEntry(ctx, repo, defaultBranchCommit, repo.DefaultBranch, folder, entry)
}
return workflows, nil
}
func GetActionWorkflow(ctx context.Context, gitrepo *git.Repository, repo *repo_model.Repository, workflowID string) (*api.ActionWorkflow, error) {
entries, err := ListActionWorkflows(ctx, gitrepo, repo)
defaultBranchCommit, err := gitrepo.GetBranchCommit(repo.DefaultBranch)
if err != nil {
return nil, err
}
folder, entries, err := actions.ListWorkflows(defaultBranchCommit)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.ID == workflowID {
return entry, nil
if entry.Name() == workflowID {
return getActionWorkflowEntry(ctx, repo, defaultBranchCommit, repo.DefaultBranch, folder, entry), nil
}
}

View File

@@ -60,8 +60,8 @@ func TestPullCreate_CommitStatus(t *testing.T) {
commitstatus.CommitStatusPending,
commitstatus.CommitStatusError,
commitstatus.CommitStatusFailure,
commitstatus.CommitStatusSuccess,
commitstatus.CommitStatusWarning,
commitstatus.CommitStatusSuccess,
}
statesIcons := map[commitstatus.CommitStatusState]string{

View File

@@ -14,6 +14,7 @@ import (
"testing"
"time"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo"
@@ -1150,6 +1151,10 @@ func Test_WebhookWorkflowRun(t *testing.T) {
testWorkflowRunEventsOnCancellingAbandonedRun(t, webhookData, false)
},
},
{
name: "WorkflowRunOnStoppingEndlessTasksForMultipleRuns",
testFunc: testWorkflowRunOnStoppingEndlessTasksForMultipleRuns,
},
}
for _, obj := range testCases {
t.Run(obj.name, func(t *testing.T) {
@@ -1586,6 +1591,84 @@ jobs:
assert.Equal(t, "user2/"+repoName, webhookData.payloads[1].Repo.FullName)
}
func testWorkflowRunOnStoppingEndlessTasksForMultipleRuns(t *testing.T, webhookData *workflowRunWebhook) {
defer test.MockVariableValue(&setting.Actions.EndlessTaskTimeout, time.Second)()
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, "user2")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
repoName := "test-workflow-run-stop-endless-tasks"
testRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: createActionsTestRepo(t, token, repoName, false).ID})
testAPICreateWebhookForRepo(t, session, "user2", repoName, webhookData.URL, "workflow_run")
runners := make([]*mockRunner, 2)
for i := range runners {
runners[i] = newMockRunner()
runners[i].registerAsRepoRunner(t, "user2", repoName, fmt.Sprintf("mock-runner-%d", i), []string{"ubuntu-latest"}, false)
}
workflowPath1 := ".gitea/workflows/endless-1.yml"
workflowPath2 := ".gitea/workflows/endless-2.yml"
workflowContent1 := `name: endless-1
on:
push:
paths:
- '.gitea/workflows/endless-1.yml'
jobs:
job-1:
runs-on: ubuntu-latest
steps:
- run: echo 'job-1'
`
workflowContent2 := `name: endless-2
on:
push:
paths:
- '.gitea/workflows/endless-2.yml'
jobs:
job-2:
runs-on: ubuntu-latest
steps:
- run: echo 'job-2'
`
opts1 := getWorkflowCreateFileOptions(user2, testRepo.DefaultBranch, "create "+workflowPath1, workflowContent1)
createWorkflowFile(t, token, "user2", repoName, workflowPath1, opts1)
opts2 := getWorkflowCreateFileOptions(user2, testRepo.DefaultBranch, "create "+workflowPath2, workflowContent2)
createWorkflowFile(t, token, "user2", repoName, workflowPath2, opts2)
task1 := runners[0].fetchTask(t)
task2 := runners[1].fetchTask(t)
_, job1, _ := getTaskAndJobAndRunByTaskID(t, task1.Id)
_, job2, _ := getTaskAndJobAndRunByTaskID(t, task2.Id)
require.NotEqual(t, job1.RunID, job2.RunID)
initialRunEventsLen := len(webhookData.payloads)
time.Sleep(2 * time.Second)
require.NoError(t, actions.StopEndlessTasks(t.Context()))
require.Len(t, webhookData.payloads, initialRunEventsLen+2)
var completedRunIDs []int64
for _, payload := range webhookData.payloads[initialRunEventsLen:] {
assert.Equal(t, "completed", payload.Action)
assert.Equal(t, "completed", payload.WorkflowRun.Status)
completedRunIDs = append(completedRunIDs, payload.WorkflowRun.ID)
}
assert.Len(t, completedRunIDs, 2)
assert.Contains(t, completedRunIDs, job1.RunID)
assert.Contains(t, completedRunIDs, job2.RunID)
run1 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: job1.RunID})
run2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: job2.RunID})
assert.Equal(t, actions_model.StatusFailure, run1.Status)
assert.Equal(t, actions_model.StatusFailure, run2.Status)
}
func testWebhookWorkflowRun(t *testing.T, webhookData *workflowRunWebhook) {
// 1. create a new webhook with special webhook for repo1
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})