Is there any smart way to determine the default branch in GitHub actions?
Now I need to write something like:
on:
push:
branches:
- master
is there a way to write something like the code below?
on:
push:
branches:
- $default-branch
I tried to google but found nothing
I accidentally found a really nice way to solve this. That evalutes to the branch name, e.g. master.
${{ github.event.repository.default_branch }}
Also, found out the hard way that that always() is side-effecting: my job was getting skipped if always() was not called even though the other clause was true.
This works to run a job only when running on default branch
if: ${{ always() && format('refs/heads/{0}', ) == github.ref }}
$default-branch can be used in Workflow templates, but not in Workflows. The branch will become hard-coded in the Workflow upon initialization, and will have to be manually maintained. [1]
Blog post: https://github.blog/changelog/2020-07-22-github-actions-better-support-for-alternative-default-branch-names/
- if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
run: echo "On the default branch"
- if: github.ref != format('refs/heads/{0}', github.event.repository.default_branch)
run: echo "Not on the default branch"
This is not possible at the moment. Please check this topic on github community
You simply can reach variable at this level
The workflow is not valid. .github/workflows/so-004-variables-in-trigger.yaml (Line: 7, Col: 9): Unrecognized named-value: 'env'. Located at position 1 within expression: env.default-branch
You may consider addition filterint based on the branch name like here but at the moment you can't do what you want.
Add this step to your job:
- name: Determine default branch
run: |
DEFAULT_BRANCH=$(git remote show origin | awk '/HEAD branch/ {print $NF}')
echo "default_branch=$DEFAULT_BRANCH" >> $GITHUB_ENV
echo "default_branch_ref=refs/heads/$DEFAULT_BRANCH" >> $GITHUB_ENV
That will add a default_branch and a default_branch_ref variable to the env enivronment variables.
You can then access the default branch name with ${{ env.default_branch }} in subsequent steps.
The default_branch_ref variable is useful for directly comparing against github.ref to determine whether you are on the default branch.
This method uses the current method of setting environment variables to use in later steps [1] and JoeLinux's method for determining the default branch name [2].
Full example workflow:
name: ci
on: [push, pull_request]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v2
- name: Determine default branch
run: |
DEFAULT_BRANCH=$(git remote show origin | awk '/HEAD branch/ {print $NF}')
echo "default_branch=$DEFAULT_BRANCH" >> $GITHUB_ENV
echo "default_branch_ref=refs/heads/$DEFAULT_BRANCH" >> $GITHUB_ENV
- name: debug
run: echo ${{ env.default_branch }}
- name: Deploy
if: github.ref == env.default_branch_ref
run: echo "Run!"
You can use $default-branch in a template, and then when that template is rendered into a new repo, it will be replaced with the (then) default branch name for the repo, but that is a very limited use case and still does not help you when the name of the default branch changes. The best I have come up with is to list the all the default branch names in the organization, like this:
on:
push:
branches:
- master
- main
- root
- default
- production
and then you can either trust that the repos will not have non-default branches with those names, or start the jobs and then filter them by adding an if condition like
if: github.event.push.ref == format('refs/heads/{}', github.event.repository.default_branch)
Side note
For most events
${{ github.event.repository.default_branch }}
is available and works fine, but not when running schedule events via cron. When github.event_name == "schedule" the only element in github.event is schedule (the cron string that triggered the run).
When running inside a GitHub action on at GitHub runner with gh available, this more reliably gets you the default branch name:
gh repo view --json defaultBranchRef --jq .defaultBranchRef.name
However, this does not help the OP when you want to make the default branch the target that triggers the run.
Hopefully, there will be a better way to do this in the future. Until then, you can use the GitHub API and save the result in a named step output.
e.g.
- name: Extract default branch name
shell: bash
run: |
owner="my-org"
repo="repo_x"
branch=$(curl -L -H 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
https://api.github.com/repos/${owner}/${repo} \
| jq .default_branch)
echo "##[set-output name=default_branch;]$(echo ${branch})"
id: repo_x
...
${{ steps.repo_x.outputs.default_branch }}
Related
I am trying to setup a variable in my CI pipeline that I will reuse later (eventually in another job, which I don't know if possible since I don't know if jobs shares variables.. but this is another problem). My pipeline is:
name: CI
on:
pull_request:
branches:
- main
jobs:
test-job:
runs-on: ubuntu-latest
name: test-job
steps:
- name: setup env variable
run: |
BRANCH_NAME=`echo "${{github.head_ref}}"'`
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
echo ${{ env.BRANCH_NAME }}
that last echo doesn't show anything unfortunately. I am sure that BRANCH_NAME is correctly set because before pushing it into the $GITHUB_ENV" I did echo it and it contains data. Plus you can see the name of the branch in the console logs.
Console logs from Github are the following:
1. Run BRANCH_NAME=`echo "test_branch"'`
2. BRANCH_NAME=test_branch >> /home/runner/work/_temp/_runner_file_commands/set_env_9eeeac39-f573-4079-ba62-e1c2019f7aff
3.
So, that final echo ${{ env.BRANCH_NAME }} gives no result. What am I missing?
UPDATE:
As suggested in the comments, I started using workflow variables, in such a way that they are available throughout all the jobs.
The initial setup becomes:
name: CI
on:
pull_request:
branches:
- main
env:
BRANCH_NAME: ""
jobs:
...
I don't like the fact I need to give those variables an empty string value as placeholder and would have preferred declaring and assigning them in one of the jobs itself.. but still. So, now variables are declared before the jobs section, how do I assign a value to them in one of my steps? Meaning, I need to replace the
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
tried already
echo "BRANCH_NAME=$BRANCH_NAME >> ${{ env.BRANCH_NAME }}
or
${{ env.BRANCH_NAME }}=$BRANCH_NAME
but both ways don't work.
So basically what I try to do is as following
if there is a push to repository A trigger the pipeline of repository B with an input thatis the the name of the branch in repository A that is pushed to. Lets call it $branchname
So what I did was a bit googling and saw that I need to add a workflow_dispatch in the B's workflow file and add some inputs. That work nice. But...
I still want people to be able to push to repository B from branches in B and trigger its pipeline with $branchname equal to a value of "dev".
I did the following in
jobs:
build:
steps:
uses: ...
....
- name: my script
run: |
if [[ -v ${{ inputs.branchname }} ]]; then value=${{ inputs.branchname }};else value="dev"; fi
...
But in the code above the value of branchname is empty for pushes from repository B. Since ${{ inputs.branchname }} is empty. So i need to find a way to be able to map the workflow_dispatch variable to a env variable to be able to use it regardless if the push is in repo A or repo B.
Do you know any way?
Please help.
Thanks in advance
I solved the problem by:
jobs:
build:
steps:
uses: ...
....
- name: my script
run: |
my__first_var=${{ inputs.branchname }}
if [[ -v my_var ]];
my_second_var="$my__first_var"
else
my_second_var="dev"
fi
...
I have two reusable workflows to deploy and destroy GCP resources, which I call from one workflow based on different conditions.
One workflow creates infra and is triggered when the label preview is added to a PR:
on:
pull_request:
types: [opened, reopened, labeled]
jobs:
create-infrastructure:
if: ${{ contains( github.event.pull_request.labels.*.name, 'preview') }}
# Call to a reusable workflow here
The second workflow I need to trigger when the PR is closed or when a specific label is removed; I tried this:
on:
pull_request:
types: [ closed, unlabeled ]
jobs:
destroy_preview:
if: ${{ contains( github.event.pull_request.labels.*.name, 'preview') }}
uses: myrepo/.github/workflows/preview-app-destroy.yml#v0.3.6
with:
project_id: xxx
I don't know how to define unlabeled for a specific label. It would be great if someone has any idea.
The pull request webhook payload doesn't contain the removed label, as far as I can tell, but you can fetch the list of issue events (which work for pull requests, too), filter by unlabeled events, and then look at the label name of the last one.
Using the GitHub CLI in a run step, that might look something like this:
name: Preview removed workflow
on:
pull_request:
types:
- unlabeled
jobs:
destroy_preview:
runs-on: ubuntu-20.04
steps:
- name: Check if "preview" was removed
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
pr=${{ github.event.number }}
label=$(gh api "repos/$GITHUB_REPOSITORY/issues/$pr/events" \
--jq 'map(select(.event == "unlabeled"))[-1].label.name')
if [[ $label == 'preview' ]]; then
echo "The 'preview' label has been removed"
fi
where you'd replace the echo with your infrastructure commands.
Now, if you want to call a reusable workflow when that specific label is removed, you have to somehow find a way to add a condition to the job where the reusable workflow is called.
One option is to make two jobs, one to check the condition and setting the result as a job output. The other job is set up as depending on the first one, and its if condition checks if the output was set to true.
This would look something like this (omitting the name and trigger, as they are identical to above):
jobs:
checklabel:
runs-on: ubuntu-20.04
outputs:
waspreview: ${{ steps.check.outputs.preview }}
steps:
- name: Check if "preview" was removed
id: check
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
pr=${{ github.event.number }}
label=$(gh api "repos/$GITHUB_REPOSITORY/issues/$pr/events" \
--jq 'map(select(.event == "unlabeled"))[-1].label.name')
if [[ $label == 'preview' ]]; then
echo "::set-output name=preview::true"
fi
destroy_preview:
needs: checklabel
if: needs.checklabel.outputs.waspreview
uses: myrepo/.github/workflows/preview-app-destroy.yml#v0.3.6
with:
project_id: xxx
Let's say I have a pull request like this.
name: Workflow
on:
pull_request:
paths:
- '**/*.h'
- '**/*.c'
I protect the master branch by configuring GitHub Actions to require the status check to pass before the pull request is mergable.
Now I update a readme doc. I open a pull request against master. The pull request is unmergable because the status check never returns a success nor does it return a fail.
Suggestions?
The docs suggest creating another action with the same name, that runs when the opposite condition is met. In your case, you'd want
name: Workflow
on:
pull_request:
paths-ignore:
- '**/*.h'
- '**/*.c'
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: 'echo "No build required" '
Notice the paths-ignore.
https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
The answer provided by the docs (Vicky's answer) doesn't work correctly for me. Providing multiple values for paths-ignore isn't preventing the generic/duplicate workflow from running. Since the original and the duplicate/generic workflow both run, we're still able to merge before the check finishes unfortunately.
This commit on Github shows a viable way of handling it that isn't too complicated:
https://github.com/android-password-store/Android-Password-Store/commit/0c45bffaba1192ea8a10173e709f439dbf32b089#diff-87ee5504a3e25ac558b343724c905f2f7949e8cec3d92b9c4300bb922afa164f
The relevant snippet (under "steps"):
- name: Check if relevant files have changed
uses: actions/github-script#0.9.0
id: service-changed
with:
result-encoding: string
script: |
const result = await github.pulls.listFiles({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
pull_number: context.payload.number,
per_page: 100
})
const serviceChanged = result.data.filter(f => f.filename.startsWith("app/") || f.filename.endsWith("gradle") || f.filename.startsWith(".github") || f.filename.startsWith("gradle") || f.filename.endsWith("properties")).length > 0
console.log(serviceChanged)
return serviceChanged
- name: Checkout repository
if: ${{ steps.service-changed.outputs.result == 'true' }}
uses: actions/checkout#v1
- name: Copy CI gradle.properties
if: ${{ steps.service-changed.outputs.result == 'true' }}
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties
Here are some other options I've found that look less viable or much more complicated:
https://github.com/fkirc/skip-duplicate-actions Looks questionable at solving this particular problem
https://brunoscheufler.com/blog/2022-04-24-required-github-actions-jobs-in-a-monorepo Looks awfully complicated but viable
https://blog.pantsbuild.org/skipping-github-actions-jobs-without-breaking-branch-protection/ Looks complicated, and it turns into a sales pitch
In gitlab we have support for CI_COMMIT_BEFORE_SHA and CI_COMMIT_SHA
Whereas in github we only have support for GITHUB_REF which holds SHA of current commit that triggered this action.
My requirement is to find if a particular file changed since last Action execution
Found a way to solve this using event payload - see push event payload:
in .github/workflows/release.yaml
name: release
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v2
- name: deploy
run: sh deploy.sh
env:
CI_COMMIT_BEFORE_SHA: ${{ github.event.before }}
CI_COMMIT_SHA: ${{ github.event.after }}
in bin/deploy.sh
HAS_DESIRED_CHANGES=`git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep -E 'path/to/file1|path/to/dir2'`
echo $HAS_DESIRED_CHANGES
HAS_DESIRED_CHANGES=`echo ${#HAS_DESIRED_CHANGES}`
if ! [ $HAS_DESIRED_CHANGES -eq 0 ]
then
echo "Has desired changes, proceed performing necessary actions"
else
echo "No desired changes. Skipping...."
fi