├── .gitignore ├── Dockerfile ├── test ├── Dockerfile ├── checkout_helper.bash ├── fail-safe.bats └── fail-fast.bats ├── run-tests.sh ├── CHANGELOG.adoc ├── .github └── workflows │ ├── main.yaml │ ├── fail-safe-commit-filter-separator-should-success.yaml │ ├── fail-safe-check-job-should-success-2.yaml │ ├── fail-safe-commit-filter-separator-should-success-2.yaml │ ├── fail-safe-check-job-should-success.yaml │ ├── fail-safe-should-success.yaml │ ├── fail-safe-merge-commit-should-success.yaml │ └── fail-safe-commit-filter-should-success.yaml ├── LICENSE ├── action.yml ├── script.sh └── Readme.adoc /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | 3 | build 4 | 5 | .idea 6 | 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bats/bats:v1.1.0 2 | 3 | RUN apk add git 4 | 5 | COPY . /code/ -------------------------------------------------------------------------------- /test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bats/bats:v1.1.0 2 | 3 | RUN apk add git 4 | 5 | COPY ../ /code/ -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build --tag ciskipbats:latest . 4 | 5 | docker run -v "${PWD}:/code" ciskipbats:latest /code/test -------------------------------------------------------------------------------- /test/checkout_helper.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Checkout revision $1 into directory $2 4 | function checkoutRevisionAndCopyScript() { 5 | mkdir "$BATS_TMPDIR/$2" 6 | cd "$BATS_TMPDIR/$2" 7 | git clone https://github.com/mstachniuk/ci-skip 8 | cd ci-skip 9 | git checkout "$1" 10 | pwd 11 | ls "$BATS_TEST_DIRNAME" 12 | cp "$BATS_TEST_DIRNAME/../script.sh" . 13 | } -------------------------------------------------------------------------------- /CHANGELOG.adoc: -------------------------------------------------------------------------------- 1 | = Changelog 2 | 3 | == v1.2.0 (2020.11.25) 4 | 5 | * Define action outputs 6 | * Job to check skip message instead of if condition in each step 7 | 8 | == v1.1.0 (2020.11.19) 9 | 10 | * Possibility to define more commit filters using separator 11 | 12 | == v1.0.1 (2020.11.14) 13 | 14 | * Add unit tests for script 15 | * Create `CI_SKIP` & `CI_SKIP_NOT` envs when commit filter not found 16 | 17 | == v1.0.0 (2020.11.14) 18 | 19 | * Initial release -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | hello_world_job: 9 | runs-on: ubuntu-latest 10 | name: Build 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: '0' 15 | - name: Set Env 16 | run: | 17 | echo "CI_SKIP_VER=${{ github.head_ref }}" >> $GITHUB_ENV 18 | - uses: mstachniuk/ci-skip@master 19 | with: 20 | exit-code: 11 21 | fail-fast: true 22 | - name: Check env 23 | run: | 24 | echo "CI_SKIP: $CI_SKIP" 25 | echo "CI_SKIP_NOT: $CI_SKIP_NOT" 26 | - name: Run tests 27 | run: | 28 | ./run-tests.sh -------------------------------------------------------------------------------- /.github/workflows/fail-safe-commit-filter-separator-should-success.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | check_skip: 9 | runs-on: ubuntu-latest 10 | outputs: 11 | skip: ${{ steps.result_step.outputs.result }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | # Contains 'Test skip [ci skip];[skip ci]' in message object 16 | ref: 'b5634847ae9760ee46cf8a1c928cd8689a821744' 17 | fetch-depth: 0 18 | - uses: mstachniuk/ci-skip@master 19 | with: 20 | commit-filter: '[skip ci];[ci skip]' 21 | commit-filter-separator: ';' 22 | - run: echo "::set-output name=result::${CI_SKIP}" 23 | id: result_step 24 | 25 | # this job should be skipped 26 | test_job: 27 | needs: check_skip 28 | if: ${{ needs.check_skip.outputs.skip == 'false' }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - run: exit 1 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 mstachniuk 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/fail-safe-check-job-should-success-2.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | init: 9 | runs-on: ubuntu-latest 10 | outputs: 11 | skip: ${{ steps.ci-skip-step.outputs.ci-skip }} 12 | skip-not: ${{ steps.ci-skip-step.outputs.ci-skip-not }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | # Contains Add Readme in message subject 17 | ref: '84841e1cb19c7cf14edeb0b0041cead83ed2d8c7' 18 | fetch-depth: 0 19 | - id: ci-skip-step 20 | uses: mstachniuk/ci-skip@master 21 | 22 | # this job should NOT be skipped 23 | main_job: 24 | needs: init 25 | if: ${{ needs.init.outputs.skip == 'false' }} 26 | runs-on: ubuntu-latest 27 | steps: 28 | - run: | 29 | echo "Output ci-skip: ${{ needs.init.outputs.skip }}" 30 | echo "Output ci-skip-not: ${{ needs.init.outputs.skip-not }}" 31 | echo "The main_job should NOT be skipped" 32 | exit 0 33 | -------------------------------------------------------------------------------- /.github/workflows/fail-safe-commit-filter-separator-should-success-2.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | check_skip: 9 | runs-on: ubuntu-latest 10 | outputs: 11 | skip: ${{ steps.result_step.outputs.result }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | # Contains Add Readme in message subject 16 | ref: '84841e1cb19c7cf14edeb0b0041cead83ed2d8c7' 17 | fetch-depth: 0 18 | - uses: mstachniuk/ci-skip@master 19 | with: 20 | commit-filter: '[skip ci];[ci skip]' 21 | commit-filter-separator: ';' 22 | - run: echo "::set-output name=result::${CI_SKIP}" 23 | id: result_step 24 | 25 | # this job should NOT be skipped 26 | test_job: 27 | needs: check_skip 28 | if: ${{ needs.check_skip.outputs.skip == 'false' }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - run: | 32 | echo "Output ci-skip: ${{ needs.check_skip.outputs.ci-skip }}" 33 | echo "Output ci-skip-not: ${{ needs.check_skip.outputs.ci-skip-not }}" 34 | echo "The main_job should NOT be skipped" 35 | exit 0 36 | -------------------------------------------------------------------------------- /.github/workflows/fail-safe-check-job-should-success.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | init: 9 | runs-on: ubuntu-latest 10 | outputs: 11 | skip: ${{ steps.ci-skip-step.outputs.ci-skip }} 12 | skip-not: ${{ steps.ci-skip-step.outputs.ci-skip-not }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | # Contains [ci skip] in message body in merge commit 17 | ref: '0928dd4ccbb9a9d5e39791011e1ec5792c495848' 18 | fetch-depth: 0 19 | - id: ci-skip-step 20 | uses: mstachniuk/ci-skip@master 21 | - run: | 22 | echo "steps.ci-skip-step.outputs.ci-skip ${{ steps.ci-skip-step.outputs.ci-skip }} SHOULD BE TRUE " 23 | echo "steps.ci-skip-step.outputs.ci-skip-not ${{ steps.ci-skip-step.outputs.ci-skip-not }}" 24 | 25 | # this job should be skipped 26 | main_job: 27 | needs: init 28 | if: ${{ needs.init.outputs.skip == 'false' }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - run: | 32 | echo "Output ci-skip: ${{ needs.init.outputs.skip }}" 33 | echo "Output ci-skip-not: ${{ needs.init.outputs.skip-not }}" 34 | echo "The main_job should be skipped" 35 | exit 1 36 | -------------------------------------------------------------------------------- /.github/workflows/fail-safe-should-success.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | hello_world_job: 9 | runs-on: ubuntu-latest 10 | name: Should success 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | # Contains [ci skip] in message body 15 | ref: '89e4cb4c830463466868cfebb8ad6c826285c40f' 16 | fetch-depth: '0' 17 | - uses: mstachniuk/ci-skip@master 18 | - name: Check env 19 | run: | 20 | echo "CI_SKIP is $CI_SKIP" 21 | echo "CI_SKIP_NOT is $CI_SKIP_NOT" 22 | if [[ "$CI_SKIP" != true ]]; then 23 | echo "The CI_SKIP env should be set to true, but it is: $CI_SKIP" 24 | exit 1 25 | fi 26 | if [[ "$CI_SKIP_NOT" != false ]]; then 27 | echo "The CI_SKIP_NOT env should be set to false, but it is: $CI_SKIP_NOT" 28 | exit 2 29 | fi 30 | - name: Check if expression CI_SKIP 31 | if: ${{ env.CI_SKIP == 'false' }} 32 | run: | 33 | echo "This step should not be executed" 34 | echo "CI_SKIP is $CI_SKIP" 35 | exit 3 36 | - name: Check if expression CI_SKIP_NOT 37 | if: ${{ env.CI_SKIP_NOT == 'true' }} 38 | run: | 39 | echo "This step should not be executed" 40 | echo "CI_SKIP_NOT is $CI_SKIP_NOT" 41 | exit 4 42 | 43 | -------------------------------------------------------------------------------- /.github/workflows/fail-safe-merge-commit-should-success.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | hello_world_job: 9 | runs-on: ubuntu-latest 10 | name: Should success 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | # Contains [ci skip] in message body in merge commit 15 | ref: '0928dd4ccbb9a9d5e39791011e1ec5792c495848' 16 | fetch-depth: '0' 17 | - uses: mstachniuk/ci-skip@master 18 | - name: Check env 19 | run: | 20 | echo "CI_SKIP is $CI_SKIP" 21 | echo "CI_SKIP_NOT is $CI_SKIP_NOT" 22 | if [[ "$CI_SKIP" != true ]]; then 23 | echo "The CI_SKIP env should be set to true, but it is: $CI_SKIP" 24 | exit 1 25 | fi 26 | if [[ "$CI_SKIP_NOT" != false ]]; then 27 | echo "The CI_SKIP_NOT env should be set to false, but it is: $CI_SKIP_NOT" 28 | exit 2 29 | fi 30 | - name: Check if expression CI_SKIP 31 | if: ${{ env.CI_SKIP == 'false' }} 32 | run: | 33 | echo "This step should not be executed" 34 | echo "CI_SKIP is $CI_SKIP" 35 | exit 3 36 | - name: Check if expression CI_SKIP_NOT 37 | if: ${{ env.CI_SKIP_NOT == 'true' }} 38 | run: | 39 | echo "This step should not be executed" 40 | echo "CI_SKIP_NOT is $CI_SKIP_NOT" 41 | exit 4 42 | 43 | -------------------------------------------------------------------------------- /.github/workflows/fail-safe-commit-filter-should-success.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | hello_world_job: 9 | runs-on: ubuntu-latest 10 | name: Should success 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | # Contains Add Readme in message subject 15 | ref: '84841e1cb19c7cf14edeb0b0041cead83ed2d8c7' 16 | fetch-depth: '0' 17 | - uses: mstachniuk/ci-skip@master 18 | with: 19 | commit-filter: 'Add Readme' 20 | - name: Check env 21 | run: | 22 | echo "CI_SKIP is $CI_SKIP" 23 | echo "CI_SKIP_NOT is $CI_SKIP_NOT" 24 | if [[ "$CI_SKIP" != true ]]; then 25 | echo "The CI_SKIP env should be set to true, but it is: $CI_SKIP" 26 | exit 1 27 | fi 28 | if [[ "$CI_SKIP_NOT" != false ]]; then 29 | echo "The CI_SKIP_NOT env should be set to false, but it is: $CI_SKIP_NOT" 30 | exit 2 31 | fi 32 | - name: Check if expression CI_SKIP 33 | if: ${{ env.CI_SKIP == 'false' }} 34 | run: | 35 | echo "This step should not be executed" 36 | echo "CI_SKIP is $CI_SKIP" 37 | exit 3 38 | - name: Check if expression CI_SKIP_NOT 39 | if: ${{ env.CI_SKIP_NOT == 'true' }} 40 | run: | 41 | echo "This step should not be executed" 42 | echo "CI_SKIP_NOT is $CI_SKIP_NOT" 43 | exit 4 44 | 45 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'CI-SKIP-ACTION' 2 | description: 'GitHub Actions to skip build execution based on commit message' 3 | author: Marcin Stachniuk 4 | branding: 5 | color: blue 6 | icon: skip-forward 7 | inputs: 8 | fail-fast: 9 | description: 'When set to true then build will fail immediately. There is no way to mark a build success and skip next steps.' 10 | required: false 11 | default: 'false' 12 | exit-code: 13 | description: 'Sets the exit code for action when fail-fast is set to true. Default: 42.' 14 | required: false 15 | default: '42' 16 | commit-filter: 17 | description: 'Sets the text in commit messages when skip the action. Default: [ci skip]' 18 | required: false 19 | default: '[ci skip]' 20 | commit-filter-separator: 21 | description: 'Sets the commit filter separator in case multiple filters are set.' 22 | required: false 23 | default: '' 24 | outputs: 25 | ci-skip: 26 | description: 'The output ci-skip with the value true/false, default: false' 27 | value: ${{ steps.set-outputs-step.outputs.skip }} 28 | ci-skip-not: 29 | description: 'The output ci-skip-not with the value true/false, default: true' 30 | value: ${{ steps.set-outputs-step.outputs.skip-not }} 31 | runs: 32 | using: "composite" 33 | steps: 34 | - run: $GITHUB_ACTION_PATH/script.sh "${{ inputs.fail-fast }}" "${{ inputs.exit-code }}" "${{ inputs.commit-filter }}" "${{ inputs.commit-filter-separator }}" 35 | shell: bash 36 | - id: set-outputs-step 37 | run: | 38 | echo "CI_SKIP: $CI_SKIP" 39 | echo "::set-output name=skip::$CI_SKIP" 40 | echo "::set-output name=skip-not::$CI_SKIP_NOT" 41 | shell: bash 42 | 43 | -------------------------------------------------------------------------------- /script.sh: -------------------------------------------------------------------------------- 1 | 2 | fail_fast=$1 3 | exit_code=$2 4 | commit_filter=$3 5 | commit_filter_separator=$4 6 | 7 | IFS=$commit_filter_separator read -ra filters <<< "$commit_filter" 8 | 9 | # Based on: https://github.com/marketplace/actions/skip-based-on-commit-message 10 | last_commit_log=$(git log -1 --pretty=format:"%s %b") 11 | 12 | readonly local is_merge_commit=$(echo "$last_commit_log" | egrep -c "Merge [a-zA-Z0-9]* into [a-zA-Z0-9]*") 13 | if [[ "$is_merge_commit" -eq 1 ]]; then 14 | readonly local commit_id=$(echo "$last_commit_log" | sed 's/Merge //' | sed 's/ into [a-zA-Z0-9]*//') 15 | last_commit_log=$(git log -1 --pretty="%s %b" $commit_id) 16 | fi 17 | echo "Last commit log: $last_commit_log" 18 | 19 | total_filter_count=0 20 | for filter in "${filters[@]}"; do 21 | filter_count=$(echo "$last_commit_log" | fgrep -c "$filter") 22 | total_filter_count=$((filter_count+total_filter_count)) 23 | done 24 | echo "Number of occurrences of '$commit_filter': $total_filter_count" 25 | 26 | if [[ "$total_filter_count" -eq 0 ]]; then 27 | echo "The last commit log does not contains \"$commit_filter\", setting environment variables for next steps:" 28 | echo "CI_SKIP=false" 29 | echo "CI_SKIP_NOT=true" 30 | echo "CI_SKIP=false" >> $GITHUB_ENV 31 | echo "CI_SKIP_NOT=true" >> $GITHUB_ENV 32 | echo "And continue..." 33 | else 34 | if [[ $fail_fast == 'true' ]]; then 35 | echo "The last commit log contains \"$commit_filter\", exiting" 36 | # It's impossible to finish workflow with success, so exiting using exit-code 37 | exit "$exit_code" 38 | else 39 | echo "The last commit log contains \"$commit_filter\", setting environment variables for next steps:" 40 | echo "CI_SKIP=true" 41 | echo "CI_SKIP_NOT=false" 42 | echo "CI_SKIP=true" >> $GITHUB_ENV 43 | echo "CI_SKIP_NOT=false" >> $GITHUB_ENV 44 | echo "And continue..." 45 | fi 46 | fi 47 | -------------------------------------------------------------------------------- /test/fail-safe.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load checkout_helper 4 | 5 | 6 | @test "should fail safe" { 7 | mydir="repo-fail-safe-1" 8 | # Contains [ci skip] in message body 9 | checkoutRevisionAndCopyScript '89e4cb4c830463466868cfebb8ad6c826285c40f' "$mydir" 10 | export GITHUB_ENV=foo.txt; 11 | 12 | run "$BATS_TMPDIR/$mydir/ci-skip/script.sh" "false" "42" "[ci skip]" "" 13 | 14 | echo "Output:" 15 | echo "$output" 16 | actual=$(cat foo.txt | sed ':a;N;$!ba;s/\n/,/g') # replace new line with coma 17 | echo "GITHUB_ENV: $actual" 18 | [ "$actual" = "CI_SKIP=true,CI_SKIP_NOT=false" ] 19 | # [ "$output" = "debug - check output" ] 20 | [ "$status" -eq 0 ] 21 | } 22 | 23 | @test "should not fail safe doesnt contains commit filter" { 24 | mydir="repo-fail-safe-2" 25 | # Contains `Add Readme` in message subject 26 | checkoutRevisionAndCopyScript '84841e1cb19c7cf14edeb0b0041cead83ed2d8c7' "$mydir" 27 | export GITHUB_ENV=foo.txt; 28 | 29 | run "$BATS_TMPDIR/$mydir/ci-skip/script.sh" "false" "42" "[ci skip]" "" 30 | 31 | echo "Output:" 32 | echo "$output" 33 | actual=$(cat foo.txt | sed ':a;N;$!ba;s/\n/,/g') # replace new line with coma 34 | echo "GITHUB_ENV: $actual" 35 | [ "$actual" = "CI_SKIP=false,CI_SKIP_NOT=true" ] 36 | [ "$status" -eq 0 ] 37 | } 38 | 39 | @test "should fail safe custom commit filter" { 40 | mydir="repo-fail-safe-3" 41 | # Contains `Add Readme` in message subject 42 | checkoutRevisionAndCopyScript '84841e1cb19c7cf14edeb0b0041cead83ed2d8c7' "$mydir" 43 | export GITHUB_ENV=foo.txt; 44 | 45 | run "$BATS_TMPDIR/$mydir/ci-skip/script.sh" "false" "42" "Add Readme" "" 46 | 47 | echo "Output:" 48 | echo "$output" 49 | actual=$(cat foo.txt | sed ':a;N;$!ba;s/\n/,/g') # replace new line with coma 50 | echo "GITHUB_ENV: $actual" 51 | 52 | [ "$actual" = "CI_SKIP=true,CI_SKIP_NOT=false" ] 53 | [ "$status" -eq 0 ] 54 | } 55 | -------------------------------------------------------------------------------- /test/fail-fast.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load checkout_helper 4 | 5 | @test "should fail fast and return 42 exit code" { 6 | mydir="repo-commit-contains-ci-skip-1" 7 | # Contains [ci skip] in message body 8 | checkoutRevisionAndCopyScript '89e4cb4c830463466868cfebb8ad6c826285c40f' "$mydir" 9 | export GITHUB_ENV=foo.txt; 10 | 11 | run "$BATS_TMPDIR/$mydir/ci-skip/script.sh" "true" "42" "[ci skip]" "" 12 | 13 | actual=$(cat foo.txt | sed ':a;N;$!ba;s/\n/,/g') # replace new line with coma 14 | echo "Output:" 15 | echo "$output" 16 | echo "GITHUB_ENV: $actual" 17 | [ "$status" -eq 42 ] 18 | } 19 | 20 | @test "should fail fast and return 22 exit code" { 21 | mydir="repo-commit-contains-ci-skip-2" 22 | # Contains [ci skip] in message body 23 | checkoutRevisionAndCopyScript '89e4cb4c830463466868cfebb8ad6c826285c40f' "$mydir" 24 | export GITHUB_ENV=foo.txt; 25 | 26 | run "$BATS_TMPDIR/$mydir/ci-skip/script.sh" "true" "22" "[ci skip]" "" 27 | 28 | actual=$(cat foo.txt | sed ':a;N;$!ba;s/\n/,/g') # replace new line with coma 29 | echo "Output:" 30 | echo "$output" 31 | echo "GITHUB_ENV: $actual" 32 | [ "$status" -eq 22 ] 33 | } 34 | 35 | @test "should not fail fast and return 0 exit code" { 36 | mydir="repo-commit-contains-ci-skip-3" 37 | # Contains `Add Readme` in message subject 38 | checkoutRevisionAndCopyScript '84841e1cb19c7cf14edeb0b0041cead83ed2d8c7' "$mydir" 39 | export GITHUB_ENV=foo.txt; 40 | 41 | run "$BATS_TMPDIR/$mydir/ci-skip/script.sh" "true" "22" "[ci skip]" "" 42 | 43 | actual=$(cat foo.txt | sed ':a;N;$!ba;s/\n/,/g') # replace new line with coma 44 | echo "Output:" 45 | echo "$output" 46 | echo "GITHUB_ENV: $actual" 47 | [ "$status" -eq 0 ] 48 | } 49 | 50 | @test "should fail fast and return 22 exit code for custom commit filter" { 51 | mydir="repo-commit-contains-ci-skip-4" 52 | # Contains `Add Readme` in message subject 53 | checkoutRevisionAndCopyScript '84841e1cb19c7cf14edeb0b0041cead83ed2d8c7' "$mydir" 54 | export GITHUB_ENV=foo.txt; 55 | 56 | run "$BATS_TMPDIR/$mydir/ci-skip/script.sh" "true" "22" "Add" "" 57 | 58 | actual=$(cat foo.txt | sed ':a;N;$!ba;s/\n/,/g') # replace new line with coma 59 | echo "Output:" 60 | echo "$output" 61 | [ "$status" -eq 22 ] 62 | } 63 | 64 | @test "should fail fast and return 22 exit code with custom separator" { 65 | mydir="repo-commit-contains-ci-skip-5" 66 | # Contains `Test commit [ci skip];[skip ci]` in message subject 67 | checkoutRevisionAndCopyScript 'b5634847ae9760ee46cf8a1c928cd8689a821744' "$mydir" 68 | export GITHUB_ENV=foo.txt; 69 | 70 | run "$BATS_TMPDIR/$mydir/ci-skip/script.sh" "true" "22" "[skip ci];[ci skip];[skip gha]" ";" 71 | 72 | actual=$(cat foo.txt | sed ':a;N;$!ba;s/\n/,/g') # replace new line with coma 73 | echo "Output:" 74 | echo "$output" 75 | echo "GITHUB_ENV: $actual" 76 | [ "$status" -eq 22 ] 77 | } 78 | 79 | @test "should not fail fast and return 0 exit code without custom separator" { 80 | mydir="repo-commit-contains-ci-skip-6" 81 | # Contains `Test commit [ci skip];[skip ci]` in message subject 82 | checkoutRevisionAndCopyScript 'b5634847ae9760ee46cf8a1c928cd8689a821744' "$mydir" 83 | export GITHUB_ENV=foo.txt; 84 | 85 | run "$BATS_TMPDIR/$mydir/ci-skip/script.sh" "true" "22" "[skip ci];[ci skip];[skip gha]" "" 86 | 87 | actual=$(cat foo.txt | sed ':a;N;$!ba;s/\n/,/g') # replace new line with coma 88 | echo "Output:" 89 | echo "$output" 90 | echo "GITHUB_ENV: $actual" 91 | [ "$status" -eq 0 ] 92 | } 93 | -------------------------------------------------------------------------------- /Readme.adoc: -------------------------------------------------------------------------------- 1 | = CI SKIP 2 | 3 | GitHub Actions to skip build execution based on commit message. 4 | 5 | IMPORTANT: Since 8th February 2021 GitHub Actions https://github.blog/changelog/2021-02-08-github-actions-skip-pull-request-and-push-workflows-with-skip-ci/[supports skipping workflows] out of the box. This GitHub Action is not needed anymore and will be not developed anymore. This repo is in read only mode. 6 | 7 | NOTE: Thank you for using and support! Implemented issue: https://github.com/actions/runner/issues/774[#774]. 8 | 9 | Sometimes there is a need to skip build on Continuous Integration (CI) server (GitHub Actions in this case), 10 | e.g. to avoid many releases after several merges to master/main branch. 11 | This is standard functionality well known in other CI systems, but missed in GitHub Actions. 12 | That's why this is needed. 13 | 14 | If the commit message (or a merge message) contains `[ci skip]` (in subject of body) the action will stop. 15 | 16 | == Use cases 17 | 18 | === Simple fail fast 19 | 20 | The simplest use case is to fail job fast after evaluating action. 21 | 22 | .Fail fast example 23 | [source,yaml] 24 | ---- 25 | on: [push] 26 | 27 | jobs: 28 | hello_world_job: 29 | runs-on: ubuntu-latest 30 | name: Build 31 | steps: 32 | - uses: actions/checkout@v2 33 | with: 34 | # The git history is needed for good evaluation of Pull Request commit messages. 35 | # By default this action checkout only last commit. 36 | # For PR it's temporary merge commit to current master and orginal commit message is missing. 37 | # For big projects can be set to 1000 for performance reasons. 38 | fetch-depth: '0' 39 | - uses: mstachniuk/ci-skip@v1 40 | with: 41 | fail-fast: true 42 | - name: Verification 43 | run: | 44 | echo "The previous step should exiting with 42 code and this code should not run" 45 | ---- 46 | 47 | The action will immediately exit before `Validation` step returning exit code 42. 48 | You can customize this exit code: 49 | 50 | .Fail fast with custom exit code example 51 | [source,yaml] 52 | ---- 53 | [...] 54 | - uses: mstachniuk/ci-skip@v1 55 | with: 56 | fail-fast: true 57 | exit-code: 22 58 | [...] 59 | ---- 60 | 61 | Now step will immediately exit with exit code 22. 62 | If you don't like it, try fail-safe. 63 | 64 | === Fail safe 65 | 66 | This is default behaviour. 67 | Remember to check in each next step the `env.CI_SKIP` or `env.CI_SKIP_NOT` value in `if`. 68 | 69 | .Fail safe example (from: link:.github/workflows/fail-safe-should-success.yaml[.github/workflows/fail-safe-should-success.yaml]) 70 | [source,yaml] 71 | ---- 72 | on: [push] 73 | 74 | jobs: 75 | hello_world_job: 76 | runs-on: ubuntu-latest 77 | name: Should success 78 | steps: 79 | - uses: actions/checkout@v2 80 | with: 81 | fetch-depth: '0' 82 | - uses: mstachniuk/ci-skip@v1 83 | - name: Check if expression CI_SKIP 84 | # Remember to check condition in each step 85 | if: ${{ env.CI_SKIP == 'false' }} 86 | run: | 87 | echo "This step should not be executed when commit message contains [ci skip]" 88 | - name: Check if expression CI_SKIP_NOT 89 | # Remember to check condition in each step 90 | if: ${{ env.CI_SKIP_NOT == 'true' }} 91 | run: | 92 | echo "This step should not be executed when commit message contains [ci skip]" 93 | ---- 94 | 95 | In this approach the build will success when commit message contains `[ci skip]`. 96 | 97 | === Change commit filter 98 | 99 | If you like to use another marker than `[ci skip]` for skipping CI build then use `commit-filter` input. 100 | 101 | .Change commit filter example (from: link:.github/workflows/fail-safe-commit-filter-should-success.yaml[.github/workflows/fail-safe-commit-filter-should-success.yaml]) 102 | [source,yaml] 103 | ---- 104 | [...] 105 | - uses: mstachniuk/ci-skip@v1 106 | with: 107 | commit-filter: 'banana banana' 108 | [...] 109 | ---- 110 | 111 | In this case when commit message contains `banana banana` then action will be skipped. 112 | 113 | === Multiple filters 114 | 115 | If you want to use multiple filters at once, it's possible to separate them with the `commit-filter-separator` input. 116 | 117 | .Multiple commit filers example (from: link:.github/workflows/fail-safe-commit-filter-separator-should-success.yaml[.github/workflows/fail-safe-commit-filter-separator-should-success.yaml]) 118 | [source,yaml] 119 | ---- 120 | [...] 121 | - uses: mstachniuk/ci-skip@v1 122 | with: 123 | commit-filter: 'banana;orange' 124 | commit-filter-separator: ';' 125 | [...] 126 | ---- 127 | 128 | === Job to check skip message 129 | 130 | It's also possible to create a job dedicated to check skip messages. 131 | 132 | .Job to skip example (from: link:fail-safe-check-job-should-success.yaml[fail-safe-check-job-should-success.yaml]) 133 | [source,yaml] 134 | ---- 135 | [...] 136 | jobs: 137 | init: 138 | runs-on: ubuntu-latest 139 | outputs: 140 | skip: ${{ steps.ci-skip-step.outputs.ci-skip }} 141 | skip-not: ${{ steps.ci-skip-step.outputs.ci-skip-not }} 142 | steps: 143 | - uses: actions/checkout@v2 144 | with: 145 | fetch-depth: 0 146 | - id: ci-skip-step 147 | uses: mstachniuk/ci-skip@master 148 | 149 | main_job: 150 | needs: init 151 | if: ${{ needs.init.outputs.skip == 'false' }} 152 | [...] 153 | ---- 154 | 155 | In this example, `main_job` will be skipped depending on the result of `check_skip`. 156 | 157 | == Google Chrome Extension 158 | 159 | If you would decide on merge to skip or not an action the https://github.com/mstachniuk/shipkit-chrome-extension[shipkit-chrome-extension] can be helpful. 160 | 161 | == Alternatives 162 | 163 | . https://github.com/veggiemonk/skip-commit/issues/5[Just use YAML]: It doesn't work for merge commits. 164 | . https://github.com/marketplace/actions/skip-based-on-commit-message[Skip based on commit message]: It's a nice solution, but doesn't work on merge commits and it's not maintained anymore. 165 | . https://github.com/styfle/cancel-workflow-action[Cancel Workflow Action]: It can cancel jobs with a status of `queued` or `in_progress`. 166 | --------------------------------------------------------------------------------