DEV Community

Cover image for Workflow Triggering Events and Event Actions
Kostas Kalafatis
Kostas Kalafatis

Posted on • Edited on

Workflow Triggering Events and Event Actions

Understanding Repository Events

One of the more useful things we can do with GitHub Actions is to initiate workflows based on specific events. We can configure our workflows to run when a specific activity occurs within the repository, at a predetermined period, or when an event outside of GitHub occurs.

We can, for example, perform a workflow when someone pushes code to a branch, submits a pull request, or creates a new issue. But what happens when an event triggers a workflow? Before we discuss that, we need to brush up on two Git related concepts, the commit SHA, and the Git ref.

The commit SHA

The commit SHA is a unique identifier that is associated with each commit in the repository. The commit data, including the current state of the repository, are passed through the SHA-1 hash function to generate a unique 40-character hexadecimal number. This will be the commit SHA. It's a way to keep track of specific changes that were included in that commit.

The Git ref

The Git ref, short for reference, is a human-readable name that points to a specific commit in the Git repo. It's essentially an alias for a commit hash, providing a more human-friendly way to reference commits. Commonly is either a Git branch name, such as master or dev, or a Git tag, such as v0.1 or v1.2.

With that out of the way, let's see how GitHub Actions handles events under the hood.

The Workflow Trigger Pipeline

First, an event occurs in the repository. These can be various things, like a push to the repository, opening or closing a pull request, creating an issue, etc. Every event is associated with a specific commit SHA and Git ref. This ties the event to the state of the repository at a specific point in time and allows GitHub (and us) to know exactly what version of the codebase the event occurred on.

Next, GitHub will look through the .github/workflows directory for any workflow files that are associated with that event. It then runs any workflows it finds there are set up to respond to that type of event. For example, if we push a commit to the main branch of our repository, GitHub will look in the .github/workflows directory on the main branch at the commit we just pushed. It then will run any workflow files it finds there that are set up to run on a push event.

Each time a workflow runs, GitHub will automatically set the GITHUB_SHA and the GITHUB_REF environment variables in the runner machine.

Diving Deeper: Push, Pull Request and Gollum Events

Now that we have an understanding of how events trigger workflows and how they're processed by GitHub Actions, let's take a closer look at two commonly utilized events: push and pull_request. We are also going to take a look at the gollum event because it is weird and honestly, I really like weird stuff.

You can find a list of all available triggering events here.

The Push Event

The push event triggers a workflow run whenever someone pushes changes to the repository. This means that when you push changes to a branch, or even when you create a new branch or a tag, the event will trigger.

The push workflow is typically used in branch-level CI/CD actions. For example, when you push code changes to a repository, GitHub Actions can automatically build the code, run the code through a linter to check for guideline inconsistencies and run the related unit tests.

The push event will carry the commit SHA of the commit that created this event and the Git ref of the branch where the push occurred.

Let's create a new workflow that will run on push. It will run a small custom script that will display some environment variables, such as the name of the machine and the running OS, and also the commit SHA and Git ref carried by the event.

name: Environment Variables on Push
on: push
jobs:
    show-info:
        runs-on: ubuntu-latest
        steps:
        - name: Display Environment Info
          run: |
            echo "Runner Machine Name : $(hostname)"
            echo "Operating System: $(uname)"
            echo "Commit SHA: $GITHUB_SHA"
            echo "Git Ref: $GITHUB_REF"
Enter fullscreen mode Exit fullscreen mode

Now let's run this workflow. To do so, just push some code on the repository and see what happens.

When I pushed a code change in the master branch, Git created a new commit. The commit SHA is 7fadb0f and the branch is master. This also triggered my push-workflow.

Image description

Upon opening the workflow run and locating the Display Environment Info step, you’ll notice that the commit SHA and Git ref match those of the triggering commit.

Image description

Let's now try something else that will trigger this workflow. Let's create a branch. I am going to create a branch using the command line since it is easier to see the steps hidden behind the UI.

To create a branch using the command line, let's run the following commands:

# Create a new branch named 'a-branch'
git branch a-branch

# Switch to the new branch
git checkout a-branch

# Push the new branch to the remote repository
git push -u origin a-branch
Enter fullscreen mode Exit fullscreen mode

The last command, git push -u origin a-branch will trigger our workflow, since it is a push command. But, there will be a small difference from our previous run. The Git ref should change, and display a-branch since we made the push to a new branch. But, since there is no actual change in the repository's state, the commit SHA should stay the same.

Upon opening the workflow run, you will notice that the commit SHA is the same, and the Git ref now points to the new branch.

Image description

The Pull Request Event

The pull_request event triggers a workflow run whenever some activity related to a pull request occurs. This includes activities, such as opening, closing, editing and synchronizing pull requests, unlike the push event that doesn't have specific activities.

The pull_request event, is quite versatile and can be used in many core CI/CD actions. These can include running automated tests, auto-labeling or categorizing pull requests based on criteria and sending notifications to team members when a pull request is opened or merged.

The push event will carry the commit SHA of the commit that GitHub created to test if the changes in the pull request can be merged into the base branch and the Git ref of the reference created by GitHub to keep track of what would happen if a pull request was merged. You can think of such a reference as a "future" commit. GitHub uses this to determine whether the pull request will merge successfully with no merge conflicts and with all the checks passing.

Let's create a new workflow that will run on the opening of a pull request. It will again run a small custom script that will display some environment variables, such as the name of the machine and the running OS, and also the commit SHA and Git ref carried by the event. By default, this workflow will trigger when a pull request is opened, synchronized or reopened, so we don't need to do anything special with our on tag. These are called activity types and we will learn more about them in a moment.

name: Environment Variables on PR Opened
on: pull_request
jobs:
    show-info:
        runs-on: ubuntu-latest
        steps:
        - name: Display Environment Info
          run: |
            echo "Runner Machine Name : $(hostname)"
            echo "Operating System: $(uname)"
            echo "Commit SHA: $GITHUB_SHA"
            echo "Git Ref: $GITHUB_REF"
Enter fullscreen mode Exit fullscreen mode

I've pushed the YAML file for our workflow in the a-branch we created earlier and then created a pull request to the master branch. This created a pull request that is currently open.

Image description

Upon opening the workflow run and locating the Display Environment Info step, you’ll notice that the commit SHA and Git ref match those of the triggering commit.

Image description

Now you will notice that the commit SHA and the Git Ref values don't match anything in the repository. There is no commit starting with 0288de86 and no branch named 1.

Remember what we discussed previously. The commit we are viewing here is a special commit, named a merge commit.  A merge commit is a special kind of commit that has two or more parent commits, and it represents the result of merging two or more branches. This commit will become available when and if the PR is merged successfully, so we can't see it right now, since our PR is still open.

The Git Ref is a PR merge branch reference. A PR merge branch refs/pull/:prNumber/merge is a reference created by GitHub to keep track of what would happen if a pull request was merged. This is our first PR in this repo, so the :prNumber is 1, and the refs/pull/1/merge is the branch GitHub created to merge a-branch to master.

The Gollum Event

And now it's time for arguably the most weirdly named event in GitHub Actions. The gollum event is named after the Gollum creature from The Lord of the Rings. Gollum was obsessed with the One Ring and called it "precious". This is a reference to the Ruby library also named Gollum, that GitHub wikies are based on. The name Gollum was selected by the developers as a joke since Wiki pages are like the precious One Ring that needs to be protected and updated.

The gollum event triggers a workflow run whenever someone makes changes to a Wiki page. This means that when you create or update a Wiki page, the event will trigger.

The gollum event is less commonly used compared to other events like push or pull_request, but it can be valuable for maintaining documentation, and tracking changes to important repository-related information.

The gollum event will carry the commit SHA of the last commit made on the default branch and the Git ref of the default branch. The default branch is the branch that was created when we created the repository on GitHub.

Let's create a new workflow that will run when we mess around the repository wiki. It will again run a small custom script that will display some environment variables, such as the name of the machine and the running OS, and also the commit SHA and Git ref carried by the event.

name: Environment Variables on Gollum
on: gollum
jobs:
    show-info:
        runs-on: ubuntu-latest
        steps:
        - name: Display Environment Info
          run: |
            echo "Runner Machine Name : $(hostname)"
            echo "Operating System: $(uname)"
            echo "Commit SHA: $GITHUB_SHA"
            echo "Git Ref: $GITHUB_REF"
Enter fullscreen mode Exit fullscreen mode

Now let's run this workflow. To do so, just make a change on your project's wiki.

Upon opening the workflow run and locating the Display Environment Info step, you’ll notice that the commit SHA and Git ref match those of the last commit made on master and a reference to master respectively.

Image description

Understanding Event Activity Types

Event activity types are a way of specifying what kind of activity occurred for a certain event. Event activity types provide additional context and specificity to GitHub Actions workflows by categorizing the specific action that occurred within a given event trigger. This means that we can filter and respond to events based on specific activities we're interested in, making the workflows more precise and efficient.

We can use activity types to trigger different workflows based on the activity type of the event. For instance, we can run some tests and security auditing when a pull request is opened, and deploy the code when a pull request is closed and merged.

Let's create a workflow that when a pull request is closed it will add a comment "Thank you for contributing!". This workflow might come in handy with Hacktoberfest just around the corner!

name: Thank contributors

on:
  pull_request_target:
    types:
      - closed

jobs:
  thank:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - name: Comment on the pull request
        uses: peter-evans/create-or-update-comment@v3
        with:
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            Thank you for contributing! 🎉
Enter fullscreen mode Exit fullscreen mode

This workflow will run when a pull request is closed. As you can see, the on target is pull_request, and we define the types to be closed. This means that the workflow will triggered by a pull_request event that has closed as its activity type and not just any pull_request event.

Next the workflow checks if the pull request is merged. A pull request can close even if the code is not merged in the codebase. Since we consider that someone has contributed if their code is merged with the codebase, then we add this check.

If the code is merged, the create-or-update-comment action is used to comment on the pull request.

I have created a pull request and it is currently open. It is the third PR since I messed up the previous workflow...

Image description

Upon clicking Merge pull request, the workflow will run, and then we will see the comment.

Image description

After tweaking around, and failing a bunch of workflow runs, I realized that you need to provide read and write permissions to your GitHub actions for it to work, so take that into consideration if you are using it outside of a playground repo.

Filtering Events

Up to this point, we have seen how to create workflows based on events, such as push, pull_request, gollum, etc., and then react to specific actions in these events, such as created, edited, deleted, etc. But what if you want more fine control? What if you want to react to specific events and actions on specific repositories, branches, or tags? How can you skip or block a workflow from running under certain circumstances?

In this section, we will take a look at how to use different types of filters in GitHub Actions.

Branch Filters

The push and pull_request events (along with the pull_request_target event), allows us to configure a workflow to run only when specific branches are targeted. These events also allow us to skip the workflow if these events target specific branches.

Including Branches

Suppose we are working on a project with multiple branches, including main (the production-ready branch), develop, (the development branch), various release branches for previous versions of the project (e.g., release/1.0, release/1.1, release/1.2, etc.). We also have various feature branches, for the current release (e.g., feature/user-auth, feature/payment-integration, etc.). Finally we have several hotfix branches, that we use for fixing issues with previous versions of the project (e.g., hotfix/1.2/fix-deadlock-in-docker-instances, etc.).

In this scenario, we want to setup a workflow that automatically runs tests whenever changes are pushed to feature branches but not on every branch push, since this would be unnecessary for most other branches.

Luckily, the push, pull_request and pull_request_target events allow us to filter a workflow based on the branch this event took place. Let's create a GitHub action that will run on pushing code only to the feature branches.

name: Feature Branch Build Workflow
on:
    push:
      branches:
        - 'feature/**'
jobs:
    build:
        runs-on: ubuntu-latest
        steps:
        - name: Checkout code
          uses: actions/checkout@v2
          
        - name: Setup Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '14'
            
        - name: Install dependencies
          run: npm install
          
        - name: Run tests
          run: npm test
          
        - name: Run linter
          run: npm run linter
Enter fullscreen mode Exit fullscreen mode

The branches key indicates which branches will trigger the workflow. So this workflow will run whenever a push event occurs on any branch that matches the pattern feature/** in the repository.

The feature/** is a glob expression that matches any branch name that starts with feature/ and has any number of characters after it. For example, it will match feature/new-feature or feature/123 but not master, dev or feature.

Excluding Branches

Keeping with our previous application, let's say that we want to run test coverage audits on our product, but we don't want to have a test coverage on the hotfix branches, since changes there are captured by the existing tests of the project. One way to do that is to ignore the release/** branches using the branches-ignore key.

Lets create a workflow that will get the coverage when someone pushes to a feature branch or opens a pull request to dev or master.

name: Feature Branch Build Workflow
on:
    push:
      branches-ignore:
        - 'hotfix/**'

    pull_request:
      branches-ignore:
        - 'hotfix/**'

jobs:
    build:
        runs-on: ubuntu-latest
        
        steps:
        - name: Checkout code
          uses: actions/checkout@v2

        - name: Setup Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '14'

        - name: Install dependencies
          run: npm install

        - name: Run tests
          run: npm test

        - name: Upload coverage to Coveralls
          env:
            CI: true
          run: npx nyc report --reporter=text-lcov | npx coveralls
Enter fullscreen mode Exit fullscreen mode

The branches-ognore key indicates which branches will trigger the workflow. So this workflow will run whenever a push event or a pull request event, occurs on any branch that does not match the pattern hotfix/** in the repository.

It is important to note that we can't use both branch and branch-ignore in the same workflow.

Tag Filters

The push and pull_request events (along with the pull_request_target event), allows us to configure a workflow to run only when specific tags are targeted. These events also allow us to skip the workflow if these events target specific tags.

The process is similar to the branch filters, so let's update our workflows to run on specific branches or specific tags. So, for including specific tags we can do something like the following:

name: Feature Branch Build Workflow
on:
    push:
      branches:
        - 'feature/**'
      tags:
        - 'v*'
jobs:
    build:
        runs-on: ubuntu-latest
        steps:
        - name: Checkout code
          uses: actions/checkout@v2
          
        - name: Setup Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '14'
            
        - name: Install dependencies
          run: npm install
          
        - name: Run tests
          run: npm test
          
        - name: Run linter
          run: npm run linter
Enter fullscreen mode Exit fullscreen mode

In this configuration, this workflow will run on pushes on any feature/** branch OR on pushes with a tag.

The same principles apply to the tags-ignore key:

name: Feature Branch Build Workflow
on:
    push:
      branches-ignore:
        - 'hotfix/**'
      tags-ignore:
        - 'beta*'

    pull_request:
      branches-ignore:
        - 'hotfix/**'
      tags-ignore:
        - 'beta*'

jobs:
    build:
        runs-on: ubuntu-latest
        
        steps:
        - name: Checkout code
          uses: actions/checkout@v2

        - name: Setup Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '14'

        - name: Install dependencies
          run: npm install

        - name: Run tests
          run: npm test

        - name: Upload coverage to Coveralls
          env:
            CI: true
          run: npx nyc report --reporter=text-lcov | npx coveralls
Enter fullscreen mode Exit fullscreen mode

In this configuration, this workflow will run on pushes on any branch or tag, except the hotfix/** branches OR tags that start with beta.

As of with the branches and branches-ignore we can't have tags and tags-ignore in the same workflow.

Conclusion

In this post, we have explored what are workflow-triggering events, which are the conditions that initiate a workflow. We have discussed some of the common events, such as push, pull_request, and gollum, and how they can be configured with different options.

We have also learned what are actions, which are the individual steps that perform the tasks in a workflow. We have seen how to use both built-in and external actions, and how to pass inputs and outputs between them.

We have created a simple workflow that comments on merged pull requests to thank the contributors. And finally, we briefly discussed filtering workflows on specific branches and tags.

And don’t forget, if you liked this post, please show some love with a heart. ❤️ Have fun, and see you on the next iteration where we are continuing our journey in the inner workings of GitHub Actions.

Top comments (0)