
Currently references a pre-release version of `code.forgejo.org/forgejo/runner/v11`, pending release of https://code.forgejo.org/forgejo/runner/pulls/1026. Fixes #5914. This PR is quite large, but it can be reviewed commit-by-commit in relatively small, logical chunks. Adds support for workflows with a `concurrency` block, and submembers `group` and `cancel-in-progress`. For example: ``` on: workflow_dispatch: jobs: rust-checks: runs-on: debian-latest steps: - run: sleep 300 concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false ``` The concurrency block effectively ends up with four supported behaviors that users will want to choose from: - Backwards compatibility / default -- if omitted completely, the existing Forgejo behavior will be implemented. That behavior is that push and pull request synchronize events will cancel all previous runs on the same repository, branch, and workflow. - Unlimited concurrency -- if the `cancel-in-progress` value is set to `false` and no `group` is provided, then the previously described Forgejo behavior will be disabled and an unlimited number of workflows can be executed simultaneously (to the maximum supported by the Forgejo Runner capacity). - Queue-behind -- if a `group` is provided and `cancel-in-progress: false` is set, then every new action run with in the same repository with the same group value will be queued behind previous workflow runs, allowing only one workflow to execute at a time in the group, but allowing all workflows to finish naturally. - Cancel-in-progress -- if a `group` is provided and `cancel-in-progress: true` is set, then every new action run with in the same repository with the same group value will cause previously queued or running runs to be cancelled, allowing only one workflow to execute at a time in the group, but preferring execution of the most recent workflow. Both the `group` and `cancel-in-progress` values can access values from the `github`, `inputs` and `vars` context for dynamic behavior. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [x] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - https://codeberg.org/forgejo/docs/pulls/1513 - [ ] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/9434): <!--number 9434 --><!--line 0 --><!--description aW1wbGVtZW50ICJjb25jdXJyZW5jeSIgYmxvY2sgaW4gRm9yZ2VqbyBBY3Rpb25zIGF0IHRoZSB3b3JrZmxvdyBsZXZlbA==-->implement "concurrency" block in Forgejo Actions at the workflow level<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9434 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net> Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
397 lines
15 KiB
Go
397 lines
15 KiB
Go
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package notify
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
|
|
actions_model "forgejo.org/models/actions"
|
|
issues_model "forgejo.org/models/issues"
|
|
packages_model "forgejo.org/models/packages"
|
|
repo_model "forgejo.org/models/repo"
|
|
user_model "forgejo.org/models/user"
|
|
"forgejo.org/modules/git"
|
|
"forgejo.org/modules/log"
|
|
"forgejo.org/modules/repository"
|
|
)
|
|
|
|
var notifiers []Notifier
|
|
|
|
// RegisterNotifier providers method to receive notify messages
|
|
func RegisterNotifier(notifier Notifier) {
|
|
go notifier.Run()
|
|
notifiers = append(notifiers, notifier)
|
|
}
|
|
|
|
// Intended for undoing RegisterNotifier in tests only, not for production usage
|
|
func UnregisterNotifier(notifier Notifier) {
|
|
notifiers = slices.DeleteFunc(notifiers, func(maybeNotifier Notifier) bool {
|
|
return notifier == maybeNotifier
|
|
})
|
|
}
|
|
|
|
// NewWikiPage notifies creating new wiki pages to notifiers
|
|
func NewWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.NewWikiPage(ctx, doer, repo, page, comment)
|
|
}
|
|
}
|
|
|
|
// EditWikiPage notifies editing or renaming wiki pages to notifiers
|
|
func EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.EditWikiPage(ctx, doer, repo, page, comment)
|
|
}
|
|
}
|
|
|
|
// DeleteWikiPage notifies deleting wiki pages to notifiers
|
|
func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.DeleteWikiPage(ctx, doer, repo, page)
|
|
}
|
|
}
|
|
|
|
// CreateIssueComment notifies issue comment related message to notifiers
|
|
func CreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository,
|
|
issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,
|
|
) {
|
|
for _, notifier := range notifiers {
|
|
notifier.CreateIssueComment(ctx, doer, repo, issue, comment, mentions)
|
|
}
|
|
}
|
|
|
|
// NewIssue notifies new issue to notifiers
|
|
func NewIssue(ctx context.Context, issue *issues_model.Issue, mentions []*user_model.User) {
|
|
for _, notifier := range notifiers {
|
|
notifier.NewIssue(ctx, issue, mentions)
|
|
}
|
|
}
|
|
|
|
// IssueChangeStatus notifies close or reopen issue to notifiers
|
|
func IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, closeOrReopen bool) {
|
|
for _, notifier := range notifiers {
|
|
notifier.IssueChangeStatus(ctx, doer, commitID, issue, actionComment, closeOrReopen)
|
|
}
|
|
}
|
|
|
|
// DeleteIssue notify when some issue deleted
|
|
func DeleteIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) {
|
|
for _, notifier := range notifiers {
|
|
notifier.DeleteIssue(ctx, doer, issue)
|
|
}
|
|
}
|
|
|
|
// MergePullRequest notifies merge pull request to notifiers
|
|
func MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
|
|
for _, notifier := range notifiers {
|
|
notifier.MergePullRequest(ctx, doer, pr)
|
|
}
|
|
}
|
|
|
|
// AutoMergePullRequest notifies merge pull request to notifiers
|
|
func AutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
|
|
for _, notifier := range notifiers {
|
|
notifier.AutoMergePullRequest(ctx, doer, pr)
|
|
}
|
|
}
|
|
|
|
// NewPullRequest notifies new pull request to notifiers
|
|
func NewPullRequest(ctx context.Context, pr *issues_model.PullRequest, mentions []*user_model.User) {
|
|
if err := pr.LoadIssue(ctx); err != nil {
|
|
log.Error("LoadIssue failed: %v", err)
|
|
return
|
|
}
|
|
if err := pr.Issue.LoadPoster(ctx); err != nil {
|
|
return
|
|
}
|
|
for _, notifier := range notifiers {
|
|
notifier.NewPullRequest(ctx, pr, mentions)
|
|
}
|
|
}
|
|
|
|
// PullRequestSynchronized notifies Synchronized pull request
|
|
func PullRequestSynchronized(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
|
|
for _, notifier := range notifiers {
|
|
notifier.PullRequestSynchronized(ctx, doer, pr)
|
|
}
|
|
}
|
|
|
|
// PullRequestReview notifies new pull request review
|
|
func PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
|
|
if err := review.LoadReviewer(ctx); err != nil {
|
|
log.Error("LoadReviewer failed: %v", err)
|
|
return
|
|
}
|
|
for _, notifier := range notifiers {
|
|
notifier.PullRequestReview(ctx, pr, review, comment, mentions)
|
|
}
|
|
}
|
|
|
|
// PullRequestCodeComment notifies new pull request code comment
|
|
func PullRequestCodeComment(ctx context.Context, pr *issues_model.PullRequest, comment *issues_model.Comment, mentions []*user_model.User) {
|
|
if err := comment.LoadPoster(ctx); err != nil {
|
|
log.Error("LoadPoster: %v", err)
|
|
return
|
|
}
|
|
for _, notifier := range notifiers {
|
|
notifier.PullRequestCodeComment(ctx, pr, comment, mentions)
|
|
}
|
|
}
|
|
|
|
// PullRequestChangeTargetBranch notifies when a pull request's target branch was changed
|
|
func PullRequestChangeTargetBranch(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest, oldBranch string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.PullRequestChangeTargetBranch(ctx, doer, pr, oldBranch)
|
|
}
|
|
}
|
|
|
|
// PullRequestPushCommits notifies when push commits to pull request's head branch
|
|
func PullRequestPushCommits(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest, comment *issues_model.Comment) {
|
|
for _, notifier := range notifiers {
|
|
notifier.PullRequestPushCommits(ctx, doer, pr, comment)
|
|
}
|
|
}
|
|
|
|
// PullReviewDismiss notifies when a review was dismissed by repo admin
|
|
func PullReviewDismiss(ctx context.Context, doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
|
|
for _, notifier := range notifiers {
|
|
notifier.PullReviewDismiss(ctx, doer, review, comment)
|
|
}
|
|
}
|
|
|
|
// UpdateComment notifies update comment to notifiers
|
|
func UpdateComment(ctx context.Context, doer *user_model.User, c *issues_model.Comment, oldContent string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.UpdateComment(ctx, doer, c, oldContent)
|
|
}
|
|
}
|
|
|
|
// DeleteComment notifies delete comment to notifiers
|
|
func DeleteComment(ctx context.Context, doer *user_model.User, c *issues_model.Comment) {
|
|
for _, notifier := range notifiers {
|
|
notifier.DeleteComment(ctx, doer, c)
|
|
}
|
|
}
|
|
|
|
// NewRelease notifies new release to notifiers
|
|
func NewRelease(ctx context.Context, rel *repo_model.Release) {
|
|
if err := rel.LoadAttributes(ctx); err != nil {
|
|
log.Error("LoadPublisher: %v", err)
|
|
return
|
|
}
|
|
for _, notifier := range notifiers {
|
|
notifier.NewRelease(ctx, rel)
|
|
}
|
|
}
|
|
|
|
// UpdateRelease notifies update release to notifiers
|
|
func UpdateRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) {
|
|
for _, notifier := range notifiers {
|
|
notifier.UpdateRelease(ctx, doer, rel)
|
|
}
|
|
}
|
|
|
|
// DeleteRelease notifies delete release to notifiers
|
|
func DeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) {
|
|
for _, notifier := range notifiers {
|
|
notifier.DeleteRelease(ctx, doer, rel)
|
|
}
|
|
}
|
|
|
|
// IssueChangeMilestone notifies change milestone to notifiers
|
|
func IssueChangeMilestone(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) {
|
|
for _, notifier := range notifiers {
|
|
notifier.IssueChangeMilestone(ctx, doer, issue, oldMilestoneID)
|
|
}
|
|
}
|
|
|
|
// IssueChangeContent notifies change content to notifiers
|
|
func IssueChangeContent(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldContent string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.IssueChangeContent(ctx, doer, issue, oldContent)
|
|
}
|
|
}
|
|
|
|
// IssueChangeAssignee notifies change content to notifiers
|
|
func IssueChangeAssignee(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, assignee *user_model.User, removed bool, comment *issues_model.Comment) {
|
|
for _, notifier := range notifiers {
|
|
notifier.IssueChangeAssignee(ctx, doer, issue, assignee, removed, comment)
|
|
}
|
|
}
|
|
|
|
// PullRequestReviewRequest notifies Request Review change
|
|
func PullRequestReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) {
|
|
for _, notifier := range notifiers {
|
|
notifier.PullRequestReviewRequest(ctx, doer, issue, reviewer, isRequest, comment)
|
|
}
|
|
}
|
|
|
|
// IssueClearLabels notifies clear labels to notifiers
|
|
func IssueClearLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) {
|
|
for _, notifier := range notifiers {
|
|
notifier.IssueClearLabels(ctx, doer, issue)
|
|
}
|
|
}
|
|
|
|
// IssueChangeTitle notifies change title to notifiers
|
|
func IssueChangeTitle(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldTitle string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.IssueChangeTitle(ctx, doer, issue, oldTitle)
|
|
}
|
|
}
|
|
|
|
// IssueChangeRef notifies change reference to notifiers
|
|
func IssueChangeRef(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldRef string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.IssueChangeRef(ctx, doer, issue, oldRef)
|
|
}
|
|
}
|
|
|
|
// IssueChangeLabels notifies change labels to notifiers
|
|
func IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue,
|
|
addedLabels, removedLabels []*issues_model.Label,
|
|
) {
|
|
for _, notifier := range notifiers {
|
|
notifier.IssueChangeLabels(ctx, doer, issue, addedLabels, removedLabels)
|
|
}
|
|
}
|
|
|
|
// CreateRepository notifies create repository to notifiers
|
|
func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) {
|
|
for _, notifier := range notifiers {
|
|
notifier.CreateRepository(ctx, doer, u, repo)
|
|
}
|
|
}
|
|
|
|
// AdoptRepository notifies the adoption of a repository to notifiers
|
|
func AdoptRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) {
|
|
for _, notifier := range notifiers {
|
|
notifier.AdoptRepository(ctx, doer, u, repo)
|
|
}
|
|
}
|
|
|
|
// MigrateRepository notifies create repository to notifiers
|
|
func MigrateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) {
|
|
for _, notifier := range notifiers {
|
|
notifier.MigrateRepository(ctx, doer, u, repo)
|
|
}
|
|
}
|
|
|
|
// TransferRepository notifies create repository to notifiers
|
|
func TransferRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, newOwnerName string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.TransferRepository(ctx, doer, repo, newOwnerName)
|
|
}
|
|
}
|
|
|
|
// DeleteRepository notifies delete repository to notifiers
|
|
func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository) {
|
|
for _, notifier := range notifiers {
|
|
notifier.DeleteRepository(ctx, doer, repo)
|
|
}
|
|
}
|
|
|
|
// ForkRepository notifies fork repository to notifiers
|
|
func ForkRepository(ctx context.Context, doer *user_model.User, oldRepo, repo *repo_model.Repository) {
|
|
for _, notifier := range notifiers {
|
|
notifier.ForkRepository(ctx, doer, oldRepo, repo)
|
|
}
|
|
}
|
|
|
|
// RenameRepository notifies repository renamed
|
|
func RenameRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldName string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.RenameRepository(ctx, doer, repo, oldName)
|
|
}
|
|
}
|
|
|
|
// PushCommits notifies commits pushed to notifiers
|
|
func PushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
|
|
for _, notifier := range notifiers {
|
|
notifier.PushCommits(ctx, pusher, repo, opts, commits)
|
|
}
|
|
}
|
|
|
|
// CreateRef notifies branch or tag creation to notifiers
|
|
func CreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.CreateRef(ctx, pusher, repo, refFullName, refID)
|
|
}
|
|
}
|
|
|
|
// DeleteRef notifies branch or tag deletion to notifiers
|
|
func DeleteRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
|
|
for _, notifier := range notifiers {
|
|
notifier.DeleteRef(ctx, pusher, repo, refFullName)
|
|
}
|
|
}
|
|
|
|
// SyncPushCommits notifies commits pushed to notifiers
|
|
func SyncPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
|
|
for _, notifier := range notifiers {
|
|
notifier.SyncPushCommits(ctx, pusher, repo, opts, commits)
|
|
}
|
|
}
|
|
|
|
// SyncCreateRef notifies branch or tag creation to notifiers
|
|
func SyncCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
|
|
for _, notifier := range notifiers {
|
|
notifier.SyncCreateRef(ctx, pusher, repo, refFullName, refID)
|
|
}
|
|
}
|
|
|
|
// SyncDeleteRef notifies branch or tag deletion to notifiers
|
|
func SyncDeleteRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
|
|
for _, notifier := range notifiers {
|
|
notifier.SyncDeleteRef(ctx, pusher, repo, refFullName)
|
|
}
|
|
}
|
|
|
|
// RepoPendingTransfer notifies creation of pending transfer to notifiers
|
|
func RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) {
|
|
for _, notifier := range notifiers {
|
|
notifier.RepoPendingTransfer(ctx, doer, newOwner, repo)
|
|
}
|
|
}
|
|
|
|
// NewUserSignUp notifies about a newly signed up user to notifiers
|
|
func NewUserSignUp(ctx context.Context, newUser *user_model.User) {
|
|
for _, notifier := range notifiers {
|
|
notifier.NewUserSignUp(ctx, newUser)
|
|
}
|
|
}
|
|
|
|
// PackageCreate notifies creation of a package to notifiers
|
|
func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
|
for _, notifier := range notifiers {
|
|
notifier.PackageCreate(ctx, doer, pd)
|
|
}
|
|
}
|
|
|
|
// PackageDelete notifies deletion of a package to notifiers
|
|
func PackageDelete(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
|
|
for _, notifier := range notifiers {
|
|
notifier.PackageDelete(ctx, doer, pd)
|
|
}
|
|
}
|
|
|
|
// ChangeDefaultBranch notifies change default branch to notifiers
|
|
func ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository) {
|
|
for _, notifier := range notifiers {
|
|
notifier.ChangeDefaultBranch(ctx, repo)
|
|
}
|
|
}
|
|
|
|
// ActionRunNowDone notifies that the old status priorStatus with (priorStatus.isDone() == false) of an ActionRun changed to run.Status with (run.Status.isDone() == true)
|
|
// run represents the new state of the ActionRun.
|
|
// lastRun represents the ActionRun of the same workflow that finished before run.
|
|
// lastRun might be nil (e.g. when the run is the first for this workflow). It is the last run of the same workflow for the same repo.
|
|
// It can be used to figure out if a successful run follows a failed one.
|
|
// Both run and lastRun need their attributes loaded.
|
|
func ActionRunNowDone(ctx context.Context, run *actions_model.ActionRun, priorStatus actions_model.Status, lastRun *actions_model.ActionRun) {
|
|
for _, notifier := range notifiers {
|
|
notifier.ActionRunNowDone(ctx, run, priorStatus, lastRun)
|
|
}
|
|
}
|