forked from gitea/gitea
		
	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:
		
							parent
							
								
									e88377470a
								
							
						
					
					
						commit
						816e46ee7c
					
				| @ -1017,7 +1017,7 @@ LEVEL = Info | ||||
| ;ALLOWED_TYPES = | ||||
| ;; | ||||
| ;; Max size of each file in megabytes. Defaults to 50MB | ||||
| ;FILE_MAX_SIZE = 50  | ||||
| ;FILE_MAX_SIZE = 50 | ||||
| ;; | ||||
| ;; Max number of files per upload. Defaults to 5 | ||||
| ;MAX_FILES = 5 | ||||
| @ -2583,6 +2583,8 @@ LEVEL = Info | ||||
| ;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 | ||||
| ;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 | ||||
| - `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 | ||||
| - `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. | ||||
| 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"` | ||||
| 		EndlessTaskTimeout    time.Duration     `ini:"ENDLESS_TASK_TIMEOUT"` | ||||
| 		AbandonedJobTimeout   time.Duration     `ini:"ABANDONED_JOB_TIMEOUT"` | ||||
| 		SkipWorkflowStrings   []string          `ìni:"SKIP_WORKFLOW_STRINGS"` | ||||
| 	}{ | ||||
| 		Enabled:           true, | ||||
| 		DefaultActionsURL: defaultActionsURLGitHub, | ||||
| 		Enabled:             true, | ||||
| 		DefaultActionsURL:   defaultActionsURLGitHub, | ||||
| 		SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"}, | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
|  | ||||
| @ -7,6 +7,7 @@ import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"slices" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	actions_model "code.gitea.io/gitea/models/actions" | ||||
| @ -20,6 +21,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/json" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	api "code.gitea.io/gitea/modules/structs" | ||||
| 	webhook_module "code.gitea.io/gitea/modules/webhook" | ||||
| 	"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) | ||||
| 	} | ||||
| 
 | ||||
| 	if skipWorkflowsForCommit(input, commit) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	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) | ||||
| @ -195,6 +201,25 @@ func notify(ctx context.Context, input *notifyInput) error { | ||||
| 	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( | ||||
| 	ctx context.Context, | ||||
| 	detectedWorkflows []*actions_module.DetectedWorkflow, | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| package integration | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| @ -18,6 +19,7 @@ import ( | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	actions_module "code.gitea.io/gitea/modules/actions" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	pull_service "code.gitea.io/gitea/services/pull" | ||||
| 	repo_service "code.gitea.io/gitea/services/repository" | ||||
| 	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})) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| 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})) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Denys Konovalov
						Denys Konovalov