mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	add skip ci functionality (#28075)
Adds the possibility to skip workflow execution if the commit message contains a string like [skip ci] or similar. The default strings are the same as on GitHub, users can also set custom ones in app.ini Reference: https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs Close #28020
This commit is contained in:
		@@ -2583,6 +2583,8 @@ LEVEL = Info
 | 
				
			|||||||
;ENDLESS_TASK_TIMEOUT = 3h
 | 
					;ENDLESS_TASK_TIMEOUT = 3h
 | 
				
			||||||
;; Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
 | 
					;; Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
 | 
				
			||||||
;ABANDONED_JOB_TIMEOUT = 24h
 | 
					;ABANDONED_JOB_TIMEOUT = 24h
 | 
				
			||||||
 | 
					;; Strings committers can place inside a commit message to skip executing the corresponding actions workflow
 | 
				
			||||||
 | 
					;SKIP_WORKFLOW_STRINGS = [skip ci],[ci skip],[no ci],[skip actions],[actions skip]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
					;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
				
			||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
					;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1396,6 +1396,7 @@ PROXY_HOSTS = *.github.com
 | 
				
			|||||||
- `ZOMBIE_TASK_TIMEOUT`: **10m**: Timeout to stop the task which have running status, but haven't been updated for a long time
 | 
					- `ZOMBIE_TASK_TIMEOUT`: **10m**: Timeout to stop the task which have running status, but haven't been updated for a long time
 | 
				
			||||||
- `ENDLESS_TASK_TIMEOUT`: **3h**: Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time
 | 
					- `ENDLESS_TASK_TIMEOUT`: **3h**: Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time
 | 
				
			||||||
- `ABANDONED_JOB_TIMEOUT`: **24h**: Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
 | 
					- `ABANDONED_JOB_TIMEOUT`: **24h**: Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
 | 
				
			||||||
 | 
					- `SKIP_WORKFLOW_STRINGS`: **[skip ci],[ci skip],[no ci],[skip actions],[actions skip]**: Strings committers can place inside a commit message to skip executing the corresponding actions workflow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path.
 | 
					`DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path.
 | 
				
			||||||
For example, `uses: actions/checkout@v3` means `https://github.com/actions/checkout@v3` since the value of `DEFAULT_ACTIONS_URL` is `github`.
 | 
					For example, `uses: actions/checkout@v3` means `https://github.com/actions/checkout@v3` since the value of `DEFAULT_ACTIONS_URL` is `github`.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,9 +22,11 @@ var (
 | 
				
			|||||||
		ZombieTaskTimeout     time.Duration     `ini:"ZOMBIE_TASK_TIMEOUT"`
 | 
							ZombieTaskTimeout     time.Duration     `ini:"ZOMBIE_TASK_TIMEOUT"`
 | 
				
			||||||
		EndlessTaskTimeout    time.Duration     `ini:"ENDLESS_TASK_TIMEOUT"`
 | 
							EndlessTaskTimeout    time.Duration     `ini:"ENDLESS_TASK_TIMEOUT"`
 | 
				
			||||||
		AbandonedJobTimeout   time.Duration     `ini:"ABANDONED_JOB_TIMEOUT"`
 | 
							AbandonedJobTimeout   time.Duration     `ini:"ABANDONED_JOB_TIMEOUT"`
 | 
				
			||||||
 | 
							SkipWorkflowStrings   []string          `ìni:"SKIP_WORKFLOW_STRINGS"`
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		Enabled:             true,
 | 
							Enabled:             true,
 | 
				
			||||||
		DefaultActionsURL:   defaultActionsURLGitHub,
 | 
							DefaultActionsURL:   defaultActionsURLGitHub,
 | 
				
			||||||
 | 
							SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import (
 | 
				
			|||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"slices"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	actions_model "code.gitea.io/gitea/models/actions"
 | 
						actions_model "code.gitea.io/gitea/models/actions"
 | 
				
			||||||
@@ -20,6 +21,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/json"
 | 
						"code.gitea.io/gitea/modules/json"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	webhook_module "code.gitea.io/gitea/modules/webhook"
 | 
						webhook_module "code.gitea.io/gitea/modules/webhook"
 | 
				
			||||||
	"code.gitea.io/gitea/services/convert"
 | 
						"code.gitea.io/gitea/services/convert"
 | 
				
			||||||
@@ -144,6 +146,10 @@ func notify(ctx context.Context, input *notifyInput) error {
 | 
				
			|||||||
		return fmt.Errorf("gitRepo.GetCommit: %w", err)
 | 
							return fmt.Errorf("gitRepo.GetCommit: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if skipWorkflowsForCommit(input, commit) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var detectedWorkflows []*actions_module.DetectedWorkflow
 | 
						var detectedWorkflows []*actions_module.DetectedWorkflow
 | 
				
			||||||
	actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
 | 
						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)
 | 
				
			||||||
@@ -195,6 +201,25 @@ func notify(ctx context.Context, input *notifyInput) error {
 | 
				
			|||||||
	return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
 | 
						return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func skipWorkflowsForCommit(input *notifyInput, commit *git.Commit) bool {
 | 
				
			||||||
 | 
						// skip workflow runs with a configured skip-ci string in commit message if the event is push or pull_request(_sync)
 | 
				
			||||||
 | 
						// https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs
 | 
				
			||||||
 | 
						skipWorkflowEvents := []webhook_module.HookEventType{
 | 
				
			||||||
 | 
							webhook_module.HookEventPush,
 | 
				
			||||||
 | 
							webhook_module.HookEventPullRequest,
 | 
				
			||||||
 | 
							webhook_module.HookEventPullRequestSync,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if slices.Contains(skipWorkflowEvents, input.Event) {
 | 
				
			||||||
 | 
							for _, s := range setting.Actions.SkipWorkflowStrings {
 | 
				
			||||||
 | 
								if strings.Contains(commit.CommitMessage, s) {
 | 
				
			||||||
 | 
									log.Debug("repo %s with commit %s: skipped run because of %s string", input.Repo.RepoPath(), commit.ID, s)
 | 
				
			||||||
 | 
									return true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func handleWorkflows(
 | 
					func handleWorkflows(
 | 
				
			||||||
	ctx context.Context,
 | 
						ctx context.Context,
 | 
				
			||||||
	detectedWorkflows []*actions_module.DetectedWorkflow,
 | 
						detectedWorkflows []*actions_module.DetectedWorkflow,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@
 | 
				
			|||||||
package integration
 | 
					package integration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
@@ -18,6 +19,7 @@ import (
 | 
				
			|||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	actions_module "code.gitea.io/gitea/modules/actions"
 | 
						actions_module "code.gitea.io/gitea/modules/actions"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	pull_service "code.gitea.io/gitea/services/pull"
 | 
						pull_service "code.gitea.io/gitea/services/pull"
 | 
				
			||||||
	repo_service "code.gitea.io/gitea/services/repository"
 | 
						repo_service "code.gitea.io/gitea/services/repository"
 | 
				
			||||||
	files_service "code.gitea.io/gitea/services/repository/files"
 | 
						files_service "code.gitea.io/gitea/services/repository/files"
 | 
				
			||||||
@@ -194,3 +196,92 @@ func TestPullRequestTargetEvent(t *testing.T) {
 | 
				
			|||||||
		assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: baseRepo.ID}))
 | 
							assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: baseRepo.ID}))
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSkipCI(t *testing.T) {
 | 
				
			||||||
 | 
						onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
				
			||||||
 | 
							user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// create the repo
 | 
				
			||||||
 | 
							repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
 | 
				
			||||||
 | 
								Name:          "skip-ci",
 | 
				
			||||||
 | 
								Description:   "test skip ci functionality",
 | 
				
			||||||
 | 
								AutoInit:      true,
 | 
				
			||||||
 | 
								Gitignores:    "Go",
 | 
				
			||||||
 | 
								License:       "MIT",
 | 
				
			||||||
 | 
								Readme:        "Default",
 | 
				
			||||||
 | 
								DefaultBranch: "main",
 | 
				
			||||||
 | 
								IsPrivate:     false,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							assert.NotEmpty(t, repo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// enable actions
 | 
				
			||||||
 | 
							err = repo_model.UpdateRepositoryUnits(db.DefaultContext, repo, []repo_model.RepoUnit{{
 | 
				
			||||||
 | 
								RepoID: repo.ID,
 | 
				
			||||||
 | 
								Type:   unit_model.TypeActions,
 | 
				
			||||||
 | 
							}}, nil)
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// add workflow file to the repo
 | 
				
			||||||
 | 
							addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
				
			||||||
 | 
								Files: []*files_service.ChangeRepoFile{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Operation:     "create",
 | 
				
			||||||
 | 
										TreePath:      ".gitea/workflows/pr.yml",
 | 
				
			||||||
 | 
										ContentReader: strings.NewReader("name: test\non:\n  push:\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - run: echo helloworld\n"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Message:   "add workflow",
 | 
				
			||||||
 | 
								OldBranch: "main",
 | 
				
			||||||
 | 
								NewBranch: "main",
 | 
				
			||||||
 | 
								Author: &files_service.IdentityOptions{
 | 
				
			||||||
 | 
									Name:  user2.Name,
 | 
				
			||||||
 | 
									Email: user2.Email,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Committer: &files_service.IdentityOptions{
 | 
				
			||||||
 | 
									Name:  user2.Name,
 | 
				
			||||||
 | 
									Email: user2.Email,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Dates: &files_service.CommitDateOptions{
 | 
				
			||||||
 | 
									Author:    time.Now(),
 | 
				
			||||||
 | 
									Committer: time.Now(),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							assert.NotEmpty(t, addWorkflowToBaseResp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// a run has been created
 | 
				
			||||||
 | 
							assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// add a file with a configured skip-ci string in commit message
 | 
				
			||||||
 | 
							addFileResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
				
			||||||
 | 
								Files: []*files_service.ChangeRepoFile{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Operation:     "create",
 | 
				
			||||||
 | 
										TreePath:      "bar.txt",
 | 
				
			||||||
 | 
										ContentReader: strings.NewReader("bar"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Message:   fmt.Sprintf("%s add bar", setting.Actions.SkipWorkflowStrings[0]),
 | 
				
			||||||
 | 
								OldBranch: "main",
 | 
				
			||||||
 | 
								NewBranch: "main",
 | 
				
			||||||
 | 
								Author: &files_service.IdentityOptions{
 | 
				
			||||||
 | 
									Name:  user2.Name,
 | 
				
			||||||
 | 
									Email: user2.Email,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Committer: &files_service.IdentityOptions{
 | 
				
			||||||
 | 
									Name:  user2.Name,
 | 
				
			||||||
 | 
									Email: user2.Email,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Dates: &files_service.CommitDateOptions{
 | 
				
			||||||
 | 
									Author:    time.Now(),
 | 
				
			||||||
 | 
									Committer: time.Now(),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							assert.NotEmpty(t, addFileResp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// the commit message contains a configured skip-ci string, so there is still only 1 record
 | 
				
			||||||
 | 
							assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID}))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user