diff --git a/models/actions/run.go b/models/actions/run.go index 9c7f049bb..5d4e3b74d 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -171,13 +171,14 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err } // CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow. -func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string) error { +func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error { // Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'. runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{ - RepoID: repoID, - Ref: ref, - WorkflowID: workflowID, - Status: []Status{StatusRunning, StatusWaiting}, + RepoID: repoID, + Ref: ref, + WorkflowID: workflowID, + TriggerEvent: event, + Status: []Status{StatusRunning, StatusWaiting}, }) if err != nil { return err diff --git a/models/actions/run_list.go b/models/actions/run_list.go index 375c46221..388bfc4f8 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -10,6 +10,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" + webhook_module "code.gitea.io/gitea/modules/webhook" "xorm.io/builder" ) @@ -71,6 +72,7 @@ type FindRunOptions struct { WorkflowID string Ref string // the commit/tag/… that caused this workflow TriggerUserID int64 + TriggerEvent webhook_module.HookEventType Approved bool // not util.OptionalBool, it works only when it's true Status []Status } @@ -98,6 +100,9 @@ func (opts FindRunOptions) ToConds() builder.Cond { if opts.Ref != "" { cond = cond.And(builder.Eq{"ref": opts.Ref}) } + if opts.TriggerEvent != "" { + cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent}) + } return cond } diff --git a/models/actions/schedule.go b/models/actions/schedule.go index 34d23f1c0..d450e7aa0 100644 --- a/models/actions/schedule.go +++ b/models/actions/schedule.go @@ -5,6 +5,7 @@ package actions import ( "context" + "fmt" "time" "code.gitea.io/gitea/models/db" @@ -118,3 +119,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error { return committer.Commit() } + +func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error { + // If actions disabled when there is schedule task, this will remove the outdated schedule tasks + // There is no other place we can do this because the app.ini will be changed manually + if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil { + return fmt.Errorf("DeleteCronTaskByRepo: %v", err) + } + // cancel running cron jobs of this repository and delete old schedules + if err := CancelRunningJobs( + ctx, + repo.ID, + repo.DefaultBranch, + "", + webhook_module.HookEventSchedule, + ); err != nil { + return fmt.Errorf("CancelRunningJobs: %v", err) + } + return nil +} diff --git a/models/git/branch.go b/models/git/branch.go index a5ee2bde6..a12cbd4de 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -292,7 +292,7 @@ func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch * } // RenameBranch rename a branch -func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { +func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(ctx context.Context, isDefault bool) error) (err error) { ctx, committer, err := db.TxContext(ctx) if err != nil { return err @@ -367,7 +367,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str } // 5. do git action - if err = gitAction(isDefault); err != nil { + if err = gitAction(ctx, isDefault); err != nil { return err } diff --git a/models/git/branch_test.go b/models/git/branch_test.go index b984244cd..b8ea663e8 100644 --- a/models/git/branch_test.go +++ b/models/git/branch_test.go @@ -4,6 +4,7 @@ package git_test import ( + "context" "testing" "code.gitea.io/gitea/models/db" @@ -132,7 +133,7 @@ func TestRenameBranch(t *testing.T) { }, git_model.WhitelistOptions{})) assert.NoError(t, committer.Commit()) - assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(isDefault bool) error { + assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(ctx context.Context, isDefault bool) error { _isDefault = isDefault return nil })) diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index 08058b0d4..ca82d54cb 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -316,29 +316,3 @@ func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error { _, err := db.GetEngine(ctx).ID(unit.ID).Update(unit) return err } - -// UpdateRepositoryUnits updates a repository's units -func UpdateRepositoryUnits(ctx context.Context, repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - // Delete existing settings of units before adding again - for _, u := range units { - deleteUnitTypes = append(deleteUnitTypes, u.Type) - } - - if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(RepoUnit)); err != nil { - return err - } - - if len(units) > 0 { - if err = db.Insert(ctx, units); err != nil { - return err - } - } - - return committer.Commit() -} diff --git a/modules/actions/github.go b/modules/actions/github.go index 278b7cb74..749bcd7c8 100644 --- a/modules/actions/github.go +++ b/modules/actions/github.go @@ -22,6 +22,7 @@ const ( GithubEventRelease = "release" GithubEventPullRequestComment = "pull_request_comment" GithubEventGollum = "gollum" + GithubEventSchedule = "schedule" ) // IsDefaultBranchWorkflow returns true if the event only triggers workflows on the default branch @@ -115,6 +116,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent return triggedEvent == webhook_module.HookEventIssueComment || triggedEvent == webhook_module.HookEventPullRequestComment + case GithubEventSchedule: + return triggedEvent == webhook_module.HookEventSchedule + default: return eventName == string(triggedEvent) } diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 60dd05f0b..b0d53b83c 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -22,7 +22,7 @@ import ( type DetectedWorkflow struct { EntryName string - TriggerEvent string + TriggerEvent *jobparser.Event Content []byte } @@ -103,6 +103,7 @@ func DetectWorkflows( commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, + detectSchedule bool, ) ([]*DetectedWorkflow, []*DetectedWorkflow, error) { entries, err := ListWorkflows(commit) if err != nil { @@ -117,6 +118,7 @@ func DetectWorkflows( return nil, nil, err } + // one workflow may have multiple events events, err := GetEventsFromContent(content) if err != nil { log.Warn("ignore invalid workflow %q: %v", entry.Name(), err) @@ -125,17 +127,18 @@ func DetectWorkflows( for _, evt := range events { log.Trace("detect workflow %q for event %#v matching %q", entry.Name(), evt, triggedEvent) if evt.IsSchedule() { - dwf := &DetectedWorkflow{ - EntryName: entry.Name(), - TriggerEvent: evt.Name, - Content: content, + if detectSchedule { + dwf := &DetectedWorkflow{ + EntryName: entry.Name(), + TriggerEvent: evt, + Content: content, + } + schedules = append(schedules, dwf) } - schedules = append(schedules, dwf) - } - if detectMatched(gitRepo, commit, triggedEvent, payload, evt) { + } else if detectMatched(gitRepo, commit, triggedEvent, payload, evt) { dwf := &DetectedWorkflow{ EntryName: entry.Name(), - TriggerEvent: evt.Name, + TriggerEvent: evt, Content: content, } workflows = append(workflows, dwf) @@ -156,7 +159,8 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web webhook_module.HookEventCreate, webhook_module.HookEventDelete, webhook_module.HookEventFork, - webhook_module.HookEventWiki: + webhook_module.HookEventWiki, + webhook_module.HookEventSchedule: if len(evt.Acts()) != 0 { log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts()) } diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 2d57f1948..c8e1e553f 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -118,6 +118,13 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on: gollum", expected: true, }, + { + desc: "HookEventSchedue(schedule) matches GithubEventSchedule(schedule)", + triggedEvent: webhook_module.HookEventSchedule, + payload: nil, + yamlOn: "on: schedule", + expected: true, + }, } for _, tc := range testCases { diff --git a/modules/webhook/type.go b/modules/webhook/type.go index 7f427f2ea..0d2aef5e1 100644 --- a/modules/webhook/type.go +++ b/modules/webhook/type.go @@ -31,6 +31,7 @@ const ( HookEventRepository HookEventType = "repository" HookEventRelease HookEventType = "release" HookEventPackage HookEventType = "package" + HookEventSchedule HookEventType = "schedule" ) // Event returns the HookEventType as an event string diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 316a1161d..bee605dd7 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -1001,7 +1001,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { } if len(units)+len(deleteUnitTypes) > 0 { - if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { + if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err) return err } diff --git a/routers/web/repo/setting/default_branch.go b/routers/web/repo/setting/default_branch.go index d0b32ef07..12e1ab7d1 100644 --- a/routers/web/repo/setting/default_branch.go +++ b/routers/web/repo/setting/default_branch.go @@ -12,8 +12,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/web/repo" - "code.gitea.io/gitea/services/context" - notify_service "code.gitea.io/gitea/services/notify" + repo_service "code.gitea.io/gitea/services/repository" ) // SetDefaultBranchPost set default branch @@ -36,23 +35,14 @@ func SetDefaultBranchPost(ctx *context.Context) { } branch := ctx.FormString("branch") - if !ctx.Repo.GitRepo.IsBranchExist(branch) { - ctx.Status(http.StatusNotFound) - return - } else if repo.DefaultBranch != branch { - repo.DefaultBranch = branch - if err := gitrepo.SetDefaultBranch(ctx, repo, branch); err != nil { - if !git.IsErrUnsupportedVersion(err) { - ctx.ServerError("SetDefaultBranch", err) - return - } - } - if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil { + if err := repo_service.SetRepoDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, branch); err != nil { + switch { + case ctx.Repo.GitRepo.IsErrBranchNotExist(err): + ctx.Status(http.StatusNotFound) + default: ctx.ServerError("SetDefaultBranch", err) - return } - - notify_service.ChangeDefaultBranch(ctx, repo) + return } log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 99c15b74f..00af58b44 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -269,7 +269,7 @@ func UnitsPost(ctx *context.Context) { return } - if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { + if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { ctx.ServerError("UpdateRepositoryUnits", err) return } diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 58e67ffae..7de2fb78e 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -123,6 +123,9 @@ func notify(ctx context.Context, input *notifyInput) error { return nil } if unit_model.TypeActions.UnitGlobalDisabled() { + if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil { + log.Error("CleanRepoScheduleTasks: %v", err) + } return nil } if err := input.Repo.LoadUnits(ctx); err != nil { @@ -167,7 +170,11 @@ func notify(ctx context.Context, input *notifyInput) error { var detectedWorkflows []*actions_module.DetectedWorkflow actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig() - workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit, input.Event, input.Payload) + workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit, + input.Event, + input.Payload, + input.Event == webhook_module.HookEventPush && input.Ref == input.Repo.DefaultBranch, + ) if err != nil { return fmt.Errorf("DetectWorkflows: %w", err) } @@ -181,7 +188,7 @@ func notify(ctx context.Context, input *notifyInput) error { continue } - if wf.TriggerEvent != actions_module.GithubEventPullRequestTarget { + if wf.TriggerEvent.Name != actions_module.GithubEventPullRequestTarget { detectedWorkflows = append(detectedWorkflows, wf) } } @@ -200,7 +207,7 @@ func notify(ctx context.Context, input *notifyInput) error { } return fmt.Errorf("gitRepo.GetCommit: %w", err) } - baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload) + baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload, false) if err != nil { return fmt.Errorf("DetectWorkflows: %w", err) } @@ -208,7 +215,7 @@ func notify(ctx context.Context, input *notifyInput) error { log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RepoPath(), baseCommit.ID) } else { for _, wf := range baseWorkflows { - if wf.TriggerEvent == actions_module.GithubEventPullRequestTarget { + if wf.TriggerEvent.Name == actions_module.GithubEventPullRequestTarget { detectedWorkflows = append(detectedWorkflows, wf) } } @@ -307,7 +314,7 @@ func handleWorkflows( IsForkPullRequest: isForkPullRequest, Event: input.Event, EventPayload: string(p), - TriggerEvent: dwf.TriggerEvent, + TriggerEvent: dwf.TriggerEvent.Name, Status: actions_model.StatusWaiting, } if need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer); err != nil { @@ -342,6 +349,7 @@ func handleWorkflows( run.RepoID, run.Ref, run.WorkflowID, + run.Event, ); err != nil { log.Error("CancelRunningJobs: %v", err) } @@ -467,8 +475,8 @@ func handleSchedules( log.Error("CountSchedules: %v", err) return err } else if count > 0 { - if err := actions_model.DeleteScheduleTaskByRepo(ctx, input.Repo.ID); err != nil { - log.Error("DeleteCronTaskByRepo: %v", err) + if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil { + log.Error("CleanRepoScheduleTasks: %v", err) } } @@ -513,19 +521,6 @@ func handleSchedules( Specs: schedules, Content: dwf.Content, } - - // cancel running jobs if the event is push - if run.Event == webhook_module.HookEventPush { - // cancel running jobs of the same workflow - if err := actions_model.CancelRunningJobs( - ctx, - run.RepoID, - run.Ref, - run.WorkflowID, - ); err != nil { - log.Error("CancelRunningJobs: %v", err) - } - } crons = append(crons, run) } diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go index 8eef2b67b..e7aa4a39a 100644 --- a/services/actions/schedule_tasks.go +++ b/services/actions/schedule_tasks.go @@ -59,6 +59,7 @@ func startTasks(ctx context.Context) error { row.RepoID, row.Schedule.Ref, row.Schedule.WorkflowID, + webhook_module.HookEventSchedule, ); err != nil { log.Error("CancelRunningJobs: %v", err) } @@ -113,6 +114,7 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) CommitSHA: cron.CommitSHA, Event: cron.Event, EventPayload: cron.EventPayload, + TriggerEvent: string(webhook_module.HookEventSchedule), ScheduleID: cron.ID, Status: actions_model.StatusWaiting, } diff --git a/services/repository/branch.go b/services/repository/branch.go index b68355324..cab1d5639 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models" + actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -23,6 +24,7 @@ import ( "code.gitea.io/gitea/modules/queue" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/timeutil" + webhook_module "code.gitea.io/gitea/modules/webhook" notify_service "code.gitea.io/gitea/services/notify" files_service "code.gitea.io/gitea/services/repository/files" @@ -357,14 +359,29 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m return "from_not_exist", nil } - if err := git_model.RenameBranch(ctx, repo, from, to, func(isDefault bool) error { + if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error { err2 := gitRepo.RenameBranch(from, to) if err2 != nil { return err2 } if isDefault { - err2 = gitrepo.SetDefaultBranch(ctx, repo, to) + // if default branch changed, we need to delete all schedules and cron jobs + if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil { + log.Error("DeleteCronTaskByRepo: %v", err) + } + // cancel running cron jobs of this repository and delete old schedules + if err := actions_model.CancelRunningJobs( + ctx, + repo.ID, + from, + "", + webhook_module.HookEventSchedule, + ); err != nil { + log.Error("CancelRunningJobs: %v", err) + } + + err2 = gitRepo.SetDefaultBranch(ctx, repo, to) if err2 != nil { return err2 } @@ -496,3 +513,50 @@ func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error { } return nil } + +func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, newBranchName string) error { + if repo.DefaultBranch == newBranchName { + return nil + } + + if !gitRepo.IsBranchExist(newBranchName) { + return git_model.ErrBranchNotExist{ + BranchName: newBranchName, + } + } + + oldDefaultBranchName := repo.DefaultBranch + repo.DefaultBranch = newBranchName + if err := db.WithTx(ctx, func(ctx context.Context) error { + if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil { + return err + } + + if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil { + log.Error("DeleteCronTaskByRepo: %v", err) + } + // cancel running cron jobs of this repository and delete old schedules + if err := actions_model.CancelRunningJobs( + ctx, + repo.ID, + oldDefaultBranchName, + "", + webhook_module.HookEventSchedule, + ); err != nil { + log.Error("CancelRunningJobs: %v", err) + } + + if err := gitRepo.SetDefaultBranch(newBranchName); err != nil { + if !git.IsErrUnsupportedVersion(err) { + return err + } + } + return nil + }); err != nil { + return err + } + + notify_service.ChangeDefaultBranch(ctx, repo) + + return nil +} diff --git a/services/repository/setting.go b/services/repository/setting.go index 7dded5d6b..6496ac401 100644 --- a/services/repository/setting.go +++ b/services/repository/setting.go @@ -5,10 +5,13 @@ package repository import ( "context" + "slices" + actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/log" ) // UpdateRepositoryUnits updates a repository's units @@ -24,6 +27,12 @@ func UpdateRepositoryUnits(ctx context.Context, repo *repo_model.Repository, uni deleteUnitTypes = append(deleteUnitTypes, u.Type) } + if slices.Contains(deleteUnitTypes, unit.TypeActions) { + if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil { + log.Error("CleanRepoScheduleTasks: %v", err) + } + } + if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(repo_model.RepoUnit)); err != nil { return err } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 01c8cf987..f2505e82c 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -21,6 +21,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/sync" asymkey_service "code.gitea.io/gitea/services/asymkey" + repo_service "code.gitea.io/gitea/services/repository" ) // TODO: use clustered lock (unique queue? or *abuse* cache) @@ -399,7 +400,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model // DeleteWiki removes the actual and local copy of repository wiki. func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error { - if err := repo_model.UpdateRepositoryUnits(ctx, repo, nil, []unit.Type{unit.TypeWiki}); err != nil { + if err := repo_service.UpdateRepositoryUnits(ctx, repo, nil, []unit.Type{unit.TypeWiki}); err != nil { return err }