Skip to content

Commit 816e46e

Browse files
authored
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
1 parent e883774 commit 816e46e

File tree

5 files changed

+124
-3
lines changed

5 files changed

+124
-3
lines changed

custom/conf/app.example.ini

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1017,7 +1017,7 @@ LEVEL = Info
10171017
;ALLOWED_TYPES =
10181018
;;
10191019
;; Max size of each file in megabytes. Defaults to 50MB
1020-
;FILE_MAX_SIZE = 50
1020+
;FILE_MAX_SIZE = 50
10211021
;;
10221022
;; Max number of files per upload. Defaults to 5
10231023
;MAX_FILES = 5
@@ -2583,6 +2583,8 @@ LEVEL = Info
25832583
;ENDLESS_TASK_TIMEOUT = 3h
25842584
;; Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
25852585
;ABANDONED_JOB_TIMEOUT = 24h
2586+
;; Strings committers can place inside a commit message to skip executing the corresponding actions workflow
2587+
;SKIP_WORKFLOW_STRINGS = [skip ci],[ci skip],[no ci],[skip actions],[actions skip]
25862588

25872589
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25882590
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

docs/content/administration/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,7 @@ PROXY_HOSTS = *.github.com
13961396
- `ZOMBIE_TASK_TIMEOUT`: **10m**: Timeout to stop the task which have running status, but haven't been updated for a long time
13971397
- `ENDLESS_TASK_TIMEOUT`: **3h**: Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time
13981398
- `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
1399+
- `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
13991400

14001401
`DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path.
14011402
For example, `uses: actions/checkout@v3` means `https://github.com/actions/checkout@v3` since the value of `DEFAULT_ACTIONS_URL` is `github`.

modules/setting/actions.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ var (
2222
ZombieTaskTimeout time.Duration `ini:"ZOMBIE_TASK_TIMEOUT"`
2323
EndlessTaskTimeout time.Duration `ini:"ENDLESS_TASK_TIMEOUT"`
2424
AbandonedJobTimeout time.Duration `ini:"ABANDONED_JOB_TIMEOUT"`
25+
SkipWorkflowStrings []string `ìni:"SKIP_WORKFLOW_STRINGS"`
2526
}{
26-
Enabled: true,
27-
DefaultActionsURL: defaultActionsURLGitHub,
27+
Enabled: true,
28+
DefaultActionsURL: defaultActionsURLGitHub,
29+
SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"},
2830
}
2931
)
3032

services/actions/notifier_helper.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bytes"
88
"context"
99
"fmt"
10+
"slices"
1011
"strings"
1112

1213
actions_model "code.gitea.io/gitea/models/actions"
@@ -20,6 +21,7 @@ import (
2021
"code.gitea.io/gitea/modules/git"
2122
"code.gitea.io/gitea/modules/json"
2223
"code.gitea.io/gitea/modules/log"
24+
"code.gitea.io/gitea/modules/setting"
2325
api "code.gitea.io/gitea/modules/structs"
2426
webhook_module "code.gitea.io/gitea/modules/webhook"
2527
"code.gitea.io/gitea/services/convert"
@@ -144,6 +146,10 @@ func notify(ctx context.Context, input *notifyInput) error {
144146
return fmt.Errorf("gitRepo.GetCommit: %w", err)
145147
}
146148

149+
if skipWorkflowsForCommit(input, commit) {
150+
return nil
151+
}
152+
147153
var detectedWorkflows []*actions_module.DetectedWorkflow
148154
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
149155
workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit, input.Event, input.Payload)
@@ -195,6 +201,25 @@ func notify(ctx context.Context, input *notifyInput) error {
195201
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
196202
}
197203

204+
func skipWorkflowsForCommit(input *notifyInput, commit *git.Commit) bool {
205+
// skip workflow runs with a configured skip-ci string in commit message if the event is push or pull_request(_sync)
206+
// https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs
207+
skipWorkflowEvents := []webhook_module.HookEventType{
208+
webhook_module.HookEventPush,
209+
webhook_module.HookEventPullRequest,
210+
webhook_module.HookEventPullRequestSync,
211+
}
212+
if slices.Contains(skipWorkflowEvents, input.Event) {
213+
for _, s := range setting.Actions.SkipWorkflowStrings {
214+
if strings.Contains(commit.CommitMessage, s) {
215+
log.Debug("repo %s with commit %s: skipped run because of %s string", input.Repo.RepoPath(), commit.ID, s)
216+
return true
217+
}
218+
}
219+
}
220+
return false
221+
}
222+
198223
func handleWorkflows(
199224
ctx context.Context,
200225
detectedWorkflows []*actions_module.DetectedWorkflow,

tests/integration/actions_trigger_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package integration
55

66
import (
7+
"fmt"
78
"net/url"
89
"strings"
910
"testing"
@@ -18,6 +19,7 @@ import (
1819
user_model "code.gitea.io/gitea/models/user"
1920
actions_module "code.gitea.io/gitea/modules/actions"
2021
"code.gitea.io/gitea/modules/git"
22+
"code.gitea.io/gitea/modules/setting"
2123
pull_service "code.gitea.io/gitea/services/pull"
2224
repo_service "code.gitea.io/gitea/services/repository"
2325
files_service "code.gitea.io/gitea/services/repository/files"
@@ -194,3 +196,92 @@ func TestPullRequestTargetEvent(t *testing.T) {
194196
assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: baseRepo.ID}))
195197
})
196198
}
199+
200+
func TestSkipCI(t *testing.T) {
201+
onGiteaRun(t, func(t *testing.T, u *url.URL) {
202+
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
203+
204+
// create the repo
205+
repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
206+
Name: "skip-ci",
207+
Description: "test skip ci functionality",
208+
AutoInit: true,
209+
Gitignores: "Go",
210+
License: "MIT",
211+
Readme: "Default",
212+
DefaultBranch: "main",
213+
IsPrivate: false,
214+
})
215+
assert.NoError(t, err)
216+
assert.NotEmpty(t, repo)
217+
218+
// enable actions
219+
err = repo_model.UpdateRepositoryUnits(db.DefaultContext, repo, []repo_model.RepoUnit{{
220+
RepoID: repo.ID,
221+
Type: unit_model.TypeActions,
222+
}}, nil)
223+
assert.NoError(t, err)
224+
225+
// add workflow file to the repo
226+
addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
227+
Files: []*files_service.ChangeRepoFile{
228+
{
229+
Operation: "create",
230+
TreePath: ".gitea/workflows/pr.yml",
231+
ContentReader: strings.NewReader("name: test\non:\n push:\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"),
232+
},
233+
},
234+
Message: "add workflow",
235+
OldBranch: "main",
236+
NewBranch: "main",
237+
Author: &files_service.IdentityOptions{
238+
Name: user2.Name,
239+
Email: user2.Email,
240+
},
241+
Committer: &files_service.IdentityOptions{
242+
Name: user2.Name,
243+
Email: user2.Email,
244+
},
245+
Dates: &files_service.CommitDateOptions{
246+
Author: time.Now(),
247+
Committer: time.Now(),
248+
},
249+
})
250+
assert.NoError(t, err)
251+
assert.NotEmpty(t, addWorkflowToBaseResp)
252+
253+
// a run has been created
254+
assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID}))
255+
256+
// add a file with a configured skip-ci string in commit message
257+
addFileResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
258+
Files: []*files_service.ChangeRepoFile{
259+
{
260+
Operation: "create",
261+
TreePath: "bar.txt",
262+
ContentReader: strings.NewReader("bar"),
263+
},
264+
},
265+
Message: fmt.Sprintf("%s add bar", setting.Actions.SkipWorkflowStrings[0]),
266+
OldBranch: "main",
267+
NewBranch: "main",
268+
Author: &files_service.IdentityOptions{
269+
Name: user2.Name,
270+
Email: user2.Email,
271+
},
272+
Committer: &files_service.IdentityOptions{
273+
Name: user2.Name,
274+
Email: user2.Email,
275+
},
276+
Dates: &files_service.CommitDateOptions{
277+
Author: time.Now(),
278+
Committer: time.Now(),
279+
},
280+
})
281+
assert.NoError(t, err)
282+
assert.NotEmpty(t, addFileResp)
283+
284+
// the commit message contains a configured skip-ci string, so there is still only 1 record
285+
assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID}))
286+
})
287+
}

0 commit comments

Comments
 (0)