๐ GitHub
Checkout current PR branch¶
By default, actions/checkout
will checkout PRs in 'detached HEAD' state using git checkout --progress --force refs/remotes/pull/#/merge
.
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
Clone GitHub gists¶
git clone https://gist.github.com/YOUR_REPOSITORY.git
Clone GitHub wikis¶
git clone https://github.com/YOUR_USERNAME/YOUR_REPOSITORY.wiki.git
Comment on new issues and PRs with gh CLI¶
name: '๐ฌ Comment on new issue and PRs'
on:
issues:
types: [opened]
pull_request:
types: [opened]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- if: github.event_name == 'issues'
run: gh issue comment $ISSUE_NUMBER --body "Thank you for opening this issue!"
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
- if: github.event_name == 'pull_request'
run: gh issue comment $PR_NUMBER --body "Thank you for opening this pull request!"
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
Commit from workflow¶
jobs:
commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
git config --global user.name "Simon Marquis"
git config --global user.email "SimonMarquis@users.noreply.github.com"
git commit -m "Hello, World!" --allow-empty
git push
Complex conditionals¶
# Supported block scalars: `>`, `>-`, `|`, `|-`
if: >
(
github.event_name == 'pull_request'
)
||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, 'specific string')
)
# Supported block scalars: `>-`, `|-`
if: >-
${{
(
github.event_name == 'pull_request'
)
||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, 'specific string')
)
}}
Concurrency¶
concurrency:
group: ${{ github.head_ref }}
cancel-in-progress: true
concurrency:
group: '${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
Dependabot auto-merge¶
name: ๐ค Dependabot auto-merge
on: pull_request
permissions:
contents: write
pull-requests: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- id: metadata
uses: dependabot/fetch-metadata@v1
- if: contains(fromJSON('["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]'), steps.metadata.outputs.update-type)
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Note
If you use status checks to test pull requests, you should enable Require status checks to pass before merging for the target branch for Dependabot pull requests. This branch protection rule ensures that pull requests are not merged unless all the required status checks pass. For more information, see "Managing a branch protection rule."
Dependabot post update¶
name: ๐ค Dependabot post update
on:
pull_request:
# paths:
permissions:
contents: write
pull-requests: write
jobs:
baseline:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]' && startsWith(github.head_ref, 'dependabot/')
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.DEPENDABOT_PAT }}
# Setup (java/gradle/etc)
# Run (script/task/etc)
- run: |
git config --global user.name "dependabot[bot]"
git config --global user.email "49699333+dependabot[bot]@users.noreply.github.com"
git commit -am "๐ค Dependabot post update" -m "[dependabot skip]"
git push
Dependabot skip¶
To allow dependabot to rebase or force push over extra commits, add [dependabot skip]
, [skip dependabot]
, [dependabot-skip]
, or [skip-dependabot]
to the commit message.
Embedded Gists¶
<script src="https://gist.github.com/<user>/<id>.js"></script>
or a specific file:
<script src="https://gist.github.com/<user>/<id>.js?file=<filename>"></script>
Environment variables¶
name: Greeting on variable day
on: workflow_dispatch
env:
DAY_OF_WEEK: Monday
jobs:
greeting_job:
runs-on: ubuntu-latest
env:
Greeting: Hello
steps:
- run: echo "$Greeting $First_Name. Today is $DAY_OF_WEEK!"
env:
First_Name: Mona
Executing step regardless of status¶
Quote
Note: Avoid using always()
for any task that could suffer from a critical failure, for example: getting sources, otherwise the workflow may hang until it times out. If you want to run a job or step regardless of its success or failure, use the recommended alternative: success() || failure()
.
๐
steps:
- run: ...
- name: ๐
if: ${{ always() }}
run: ...
- name: ๐
if: ${{ success() || failure() }}
run: ...
Expand all diffs¶
By default, GitHub will limit the number of expanded diffs in a PR.
Run this script to force open all diffs (bookmarklet ):
document
.querySelectorAll("button.load-diff-button")
.forEach((node) => node.click());
Fork detection on PR¶
Can be misleading if the PR comes from the same repository but that repository is a fork itself!
on: pull_request
jobs:
non-fork:
if: ${{ !github.event.pull_request.head.repo.fork }}
name: Will NOT run on forks
fork-only:
if: ${{ github.event.pull_request.head.repo.fork }}
name: Will ONLY run on forks
on: pull_request
jobs:
non-fork:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
name: Will NOT run on forks
fork-only:
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
name: Will ONLY run on forks
Glob patterns checks¶
If the pattern does not match any files, this returns an empty string.
- name: 'Only if it contains any text file'
if: hashFiles('**/*.txt') != ''
run: echo "yes"
Google's Maven repository for dependabot¶
version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "daily"
registries: "*"
labels: [ ]
registries:
maven-google:
type: "maven-repository"
url: "https://maven.google.com"
replaces-base: true
Java in Codespace¶
sdk current java
Using java version 17.0.10-ms
sdk list java
================================================================================
Available Java Versions for Linux 64bit
================================================================================
Vendor | Use | Version | Dist | Status | Identifier
--------------------------------------------------------------------------------
Microsoft | | 21.0.3 | ms | | 21.0.3-ms
| | 21.0.2 | ms | installed | 21.0.2-ms
| | 21.0.1 | ms | | 21.0.1-ms
| | 17.0.11 | ms | | 17.0.11-ms
| >>> | 17.0.10 | ms | installed | 17.0.10-ms
# Use โน to autocomplete version name
sdk default java 21.0.2-ms
setting java 21.0.2-ms as the default version for all shells.
Job outputs¶
jobs:
job1:
runs-on: ubuntu-latest
outputs:
output1: ${{ steps.step1.outputs.foo }}
output2: ${{ steps.step2.outputs.baz }}
steps:
- id: step1
run: echo "foo=bar" >> $GITHUB_OUTPUT
- id: step2
run: echo "baz=qux" >> $GITHUB_OUTPUT
job2:
runs-on: ubuntu-latest
needs: job1
steps:
- run: echo ${{needs.job1.outputs.output1}} ${{needs.job1.outputs.output2}}
Job summary¶
- name: Generate Job summary
run: |
echo "# Hello, World!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY # this is a blank line
echo "- a" >> $GITHUB_STEP_SUMMARY
echo "- b" >> $GITHUB_STEP_SUMMARY
echo "- c" >> $GITHUB_STEP_SUMMARY
# Hello, World!
- a
- b
- c
Kotlin scripts caching¶
env:
KOTLIN_MAIN_KTS_COMPILED_SCRIPTS_CACHE_DIR: ${{ github.workspace }}/.main.kts
jobs:
run:
name: Kotlin script
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache Kotlin scripts
uses: actions/cache@v3
with:
path: ${{ env.KOTLIN_MAIN_KTS_COMPILED_SCRIPTS_CACHE_DIR }}
key: kts-${{ runner.os }}-${{ hashFiles('**/*.main.kts') }}
- name: Ensure the Kotlin scripts cache directory exists
run: mkdir -p "$KOTLIN_MAIN_KTS_COMPILED_SCRIPTS_CACHE_DIR"
- run: ./foo.main.kts
List changed files¶
- name: Get changed files
run: gh pr view ${{ github.event.number }} --json files -q '.files[].path'
Matching multiple steps outcome¶
Warning
A status check function call is required, otherwise an implicit success()
is will be applied.
steps:
- id: foo
run: ...
- name: ๐
if: ${{ always() && (steps.foo.outcome == "success" || steps.foo.outcome == "failure) }}
run: ...
- name: ๐
if: ${{ always() && contains(fromJSON('["success", "failure"]'), steps.foo.outcome) }}
run: ...
Matrix strategy¶
jobs:
job:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: ['11', '17', '19']
steps:
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: ${{ matrix.java-version }}
- run: java --version
Multline variables¶
Warning
Make sure the delimiter you're using won't occur on a line of its own within the value. If the value is completely arbitrary then you shouldn't use this format. Write the value to a file instead.
-
Environment variable
- run: | { echo 'RESULT<<EOF' curl https://example.com echo EOF } >> "$GITHUB_ENV"
-
Output parameter
- run: | { echo 'result<<EOF' curl https://example.com echo EOF } >> "$GITHUB_OUTPUT"
Print messages in workflow steps¶
runs-on: ubuntu-latest
steps:
- run: echo "::debug::This is a debug message"
- run: echo "::notice::This is a notice message"
- run: echo "::warning::This is a warning message"
- run: |
echo "::group::Group title"
echo "Inside group"
echo "::endgroup::"
Rename workflow at runtime¶
name: Build
run-name: Build by @${{ github.actor }}
Restrict workflow to a specific repository¶
jobs:
deploy:
if: github.repository == 'UserName/RepositoryName'
Reuse local composite action¶
name: 'Hello'
description: 'Say Hello to someone'
inputs:
who:
description: 'Who?'
default: 'World'
required: true
outputs:
result:
description: "Random number"
value: ${{ steps.compute.outputs.result }}
runs:
using: composite
steps:
- id: compute
run: echo "result=Hello, $WHO!" >> $GITHUB_OUTPUT
shell: bash
env:
WHO: ${{ inputs.who }}
on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: .github/actions/hello
with:
who: Bob
Secret file in GitHub Actions¶
Create a new secret with https://github.com/[user]/[project]/settings/secrets/actions/new
- Name:
MY_SECRET
- Value: base64 representation of the file content
base64 --wrap=0 secret.json
jobs:
my_job:
name: My Job
runs-on: ubuntu-latest
steps:
- name: Create secret.json
run: echo "$MY_SECRET" | base64 --decode > secret.json
env:
MY_SECRET: ${{ secrets.MY_SECRET }}
- name: Check file
run: stat secret.json
- name: Delete secret.json
run: rm secret.json
if: always()
Setting an environment variable¶
steps:
- name: Set the value
run: echo "foo=bar" >> $GITHUB_ENV
- name: Use the value
run: echo "${{ env.foo }}"
Setting an output parameter¶
steps:
- id: foo
run: echo "BAR=BAZ" >> $GITHUB_OUTPUT
- run: echo "${{ steps.foo.outputs.BAR }}"
Share artifacts between jobs¶
Quote
Note: You can only download artifacts in a workflow that were uploaded during the same workflow run.
๐
jobs:
save-job:
steps:
- run: echo "..." > output.txt
- uses: actions/upload-artifact@v3
with:
name: output
path: output.txt
load-job:
needs: save-job
steps:
- uses: actions/download-artifact@v3
with:
name: output
- run: cat output.txt
To download an artifact from a separate workflow run, you can use the actions/download-artifact action. For example, you can download the artifact named output-log-file.
jobs:
example-job:
steps:
- name: Download a single artifact
Skipping workflow runs¶
Quote
Workflows that would otherwise be triggered using on: push
or on: pull_request
won't be triggered if you add any of the following strings to the commit message in a push, or the HEAD commit of a pull request:
[skip ci]
[ci skip]
[no ci]
[skip actions]
[actions skip]
Alternatively, you can end the commit message with two empty lines followed by either:
skip-checks:true
skip-checks: true
To request checks for a commit, end the commit message with two empty lines followed by request-checks: true
.
Ternary operator¶
FOO: ${{ <condition> && <true> || <false> }}
Example
API_ENV: ${{ github.ref_name == 'main' && 'prod' || 'staging' }}
Warning
If the value of <true>
is falsy, the result of the expression will be <false>
.
Worflow input as command line arguments¶
on:
workflow_dispatch:
inputs:
arguments:
required: true
type: string
jobs:
gradle:
runs-on: ubuntu-latest
steps:
- run: |
IFS=" " read -ra args <<< "$ARGS"
./my-command "${args[@]}"
env:
ARGS: ${{ inputs.arguments }}
Workflow input types¶
on:
workflow_dispatch:
inputs:
name:
type: string
required: true
age:
type: number
required: true
size:
type: choice
required: true
options:
- s
- m
- l
test:
type: boolean
default: false
env:
type: environment
jobs:
job:
runs-on: ubuntu-latest
steps:
- run: echo "name=$INPUT_NAME age=$INPUT_AGE size=$INPUT_SIZE test=$INPUT_TEST env=$INPUT_ENV"
env:
INPUT_NAME: ${{ github.event.inputs.name }}
INPUT_AGE: ${{ github.event.inputs.age }}
INPUT_SIZE: ${{ github.event.inputs.size }}
INPUT_TEST: ${{ github.event.inputs.test }}
INPUT_ENV: ${{ github.event.inputs.env }}