Introduction
Hey there everyone đź‘‹ Welcome back to our GitHub Action Journey! In our previous article, “Understanding GitHub Actions”, we built our first workflow - a “Hello World” automation that ran every time we pushed code to our repository. That was a great introduction, but what if we want more control over when and how our workflows run?
That’s precisely what we’ll be exploring in this article! We’ll dive deep into two essential triggers—the mechanisms that determine what events kick off our workflows. We’ll start by taking a closer look at the push
event, which we used in our “Hello World” example. We’ll learn how to refine this trigger, giving us more control over when our workflow runs in response to code changes.
Then, we’ll explore the pull_request
trigger, one of the most commonly used triggers in real-world workflows:
- Push events: Control when your workflow runs based on code pushes
- Pull request events: Automate tasks when someone suggests changes to your code
With these triggers, you’ll be able to automate tasks like running tests only on specific branches or performing code reviews when a pull request is created.
So, how do we actually put these triggers to work? We’ll start with the push
event, since we’ve already used it in our “Hello World” example. Let’s break down how it works and see how we can tailor it to our specific needs.
The Push Event: Your First Trigger
When we’re using GitHub Actions, workflows are the backbone of automation, letting us organize the tasks we want to automate. Think of them as detailed recipes, written in YAML, that tell GitHub Actions what to do. Each workflow, like the simple “Hello World” example from our previous article, is set in motion by a trigger - a specific event that kicks off the automation process.
|
|
Figure 1: A simplified structure of a GitHub Actions workflow, highlighting the key components: Name, Triggers, Jobs, and Steps.
Let’s take a look back at that basic workflow:
|
|
This workflow, triggered by the push
event, showed us the fundamental components: a name, a trigger (on: [push]
), and the work to be done (the say-hello
job with its steps). Now, we’ll explore how to refine this push
event, giving us more control over when our workflow runs. A common need is to differentiate between code versions. When developing software, we often work with multiple branches - parallel versions of our code. This allows us to isolate new features or bug fixes from the stable “main” branch. In its simplest form, on: push
triggers the workflow on every push to any branch. But we can be more specific.
Controlling Workflow Execution with Branches
As we just discussed, the basic on: push
trigger runs our workflow whenever we push code to any branch in our repository. But GitHub Actions gives us more control than that. We can use the branches
and branches-ignore
keywords to specify exactly which branches should (or shouldn’t) trigger our workflow.
The branches
Keyword
The branches
keyword lets us create a list of branches that will trigger the workflow. Let’s say we want our workflow to run only when we push changes to the main
branch or any branch whose name starts with release/
. Here’s how we’d do it:
|
|
Let’s break this down:
on: push:
: This part is familiar - it tells GitHub Actions that we’re configuring thepush
event trigger.branches:
: This keyword indicates that we’re about to specify a list of branches.main
: This line tells GitHub Actions to trigger the workflow when changes are pushed to themain
branch.'release/**'
: This line uses a wildcard pattern. The*
means that the workflow will be triggered on any branch whose name starts withrelease/
, such asrelease/1.0
,release/2.3.4
, or evenrelease/alpha
.
With this configuration, pushes to other branches, like a development
branch or a feature branch named new-login-page
, will not trigger the workflow.
The branches-ignore
Keyword
Sometimes, it’s easier to specify which branches should not trigger the workflow. That’s where branches-ignore
comes in. Let’s say we want our workflow to run on all branches except those starting with feature/
:
|
|
In this case, pushes to branches like feature/new-ui
or feature/bug-fix
will not trigger the workflow, while pushes to main
, development
, or any other branch will trigger it.
Combining branches
and branches-ignore
We can even use branches
and branches-ignore
together for more complex scenarios. For example:
|
|
This configuration would trigger the workflow on pushes to main
and branches starting with releases/
, except for branches starting with releases/
that also end with -alpha
. So, releases/1.0
would trigger the workflow, but releases/2.0-alpha
would not.
Let’s Practice!
Now it’s your turn to try it out! Let’s modify our “Hello World” workflow to run only when we push changes to the main
branch:
- Open your
main.yml
file (it’s in the.github/workflows
directory of your repository). - Update the
on: push
section to include thebranches
keyword:
|
|
- Save the
main.yml
file. - Stage, commit, and push the changes to your GitHub repository (you can use the Source Control panel in VS Code or the command line).
Head over to the “Actions” tab of your repository, and you’ll see that the workflow has run again, but this time it was only triggered because you pushed to the main
branch!
Controlling Workflow Execution with File Paths
In any repository, not all files are created equal. Some files—such as the source code in your src
directory or critical JavaScript files—are essential to your project’s functionality, while others, like documentation (e.g., Markdown files), may be less critical when it comes to triggering tests, security scans, or other resource-intensive workflows. GitHub Actions gives you the flexibility to react differently to changes in various files using the paths
and paths-ignore
keywords. This allows you to tailor your workflows so that they only run when changes occur in the parts of your repository that truly matter.
For example, suppose you want your workflow to trigger only when there are changes to:
- A specific file (say,
src/app.js
), because it’s the heart of your application. - Any file within the
src
directory, where most of your code lives. - Or any JavaScript file in the entire repository, since these files are critical to your application logic.
You can set this up as follows:
|
|
Key Points:
-
Specific vs. General:
- Using
'src/app.js'
is very specific—only changes to that file will trigger the workflow. - Using
'src/**'
is more general and matches any file within thesrc
directory (and its subdirectories). - The pattern
'**/*.js'
matches any file ending in.js
regardless of its location in the repository.
- Using
-
Pattern Matching:
- The
**
wildcard in a pattern matches any number of directories or files. For example,'**/*.js'
explicitly indicates “any directory followed by any file ending with.js
.” This is a common and clear convention, even though GitHub Actions might also understand simpler patterns like'**.js'
.
- The
-
OR Relationship:
- The patterns listed under
paths
are evaluated with an OR condition. This means that if a push contains changes matching any one of these patterns, the workflow will trigger. You do not need a change to match all the patterns; matching one is sufficient.
- The patterns listed under
By using these options, you gain precise control over when your workflow runs—ensuring that resource-intensive processes like testing or security scanning only occur when changes are made to the parts of your repository that truly warrant them.
The paths-ignore
Keyword
Just as you can specify which file changes should trigger your workflow, you can also define which changes should not trigger it using the paths-ignore
keyword. For instance, if you want your workflow to run on all pushes except those that only modify Markdown files or files in the docs
directory, you can use:
|
|
With this configuration, if a push only includes changes to files ending in .md
or files within the docs
directory (or its subdirectories), the workflow will not run. Any other changes will trigger it.
Combining paths
and paths-ignore
For even finer control, you can combine both paths
and paths-ignore
in your workflow configuration. This allows you to specify exactly which changes should trigger the workflow and which should be ignored. For example, if you want your workflow to trigger for any changes in the src
directory except those within a src/test
subdirectory, you can write:
|
|
This configuration will trigger the workflow on any changes within the src
directory, except for changes to files within the src/test
subdirectory.
Let’s Practice!
Let’s add to our previous example. We’ll modify our workflow to run only when changes are pushed to the main
branch AND when those changes include modifications to JavaScript files.
-
Open your
main.yml
file. -
Update the
on: push
section to include bothbranches
andpaths
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
name: Hello World on: push: branches: - main paths: - '**/*.js' jobs: say-hello: runs-on: ubuntu-latest steps: - name: Say hello run: echo "Hello, GitHub Actions!" - name: Tell us the time run: date
-
Save the
main.yml
file. -
Now, let’s create a simple JavaScript file to test our updated workflow. In the root of your repository, create a new file named
test.js
and add the following line of JavaScript code:1
console.log("Hello from test.js!");
This is just a basic JavaScript command that will print a message to the console.
-
Stage, commit, and push the changes to your GitHub repository. You can use the Source Control panel in VS Code or the command line:
1 2 3
git add . git commit -m "Add test.js file" git push
Now, when you go to the “Actions” tab, you’ll see that the workflow was only triggered because you modified a JavaScript file on the main
branch. If you make changes to other file types or on other branches, the workflow won’t run!
The pull_request
Event: Automating Code Reviews
So far, we’ve explored how to trigger workflows using the push
event, which is fantastic for automating tasks based on code changes being directly added to our repository. But in many development workflows, especially when collaborating with others, changes aren’t directly pushed to the main branch. Instead, they go through a review process using pull requests.
Think of a pull request as a formal proposal to merge changes from one branch into another. It’s a way of saying, “Hey, I’ve made some changes on this branch, and I think they should be incorporated into the main branch. Can someone please review them?”
GitHub Actions provides the pull_request
event trigger, which allows us to automate tasks specifically for this code review process. This means we can set up workflows that automatically run tests, check code style, or perform other checks whenever a pull request is created or updated.
Understanding pull_request
Events
Now, pull_request
events are a bit different than the push
events we talked about earlier. Unlike push
, which simply signals a code push, pull_request
involves a lot more activities related to proposing and reviewing code changes, especially when collaborating with others. So, when we’re working with pull_request
triggers, it’s important to understand that each event actually has a specific type attached to it. This type tells us what kind of activity happened with the pull request.
Let’s start with the simplest way to use a pull_request
trigger:
|
|
When used in this way, without specifying any types, GitHub Actions will, by default, only trigger our workflow if one of the following three event types occurs:
opened
: This is when someone first creates a pull request. It’s usually a good time to run some initial tests or checks.reopened
: This is for when a pull request that was closed gets reopened. Just like when it’s first opened, you’d probably want to re-run your checks.synchronize
: Now, this one’s a bit more advanced, so we won’t go into all the details just yet. Think of it as a way to keep an eye on updates to the pull request’s code. We’ll explore this further in future articles.
These default types cover the most common situations where you’d want some automation to run when working with pull requests.
However, there are many other pull_request
event types available. Here are a few you might find useful:
closed
: This happens when a pull request is closed. It could be merged or just closed without merging - either way, this event is triggered.assigned
/unassigned
: This is triggered when someone gets assigned to a pull request or unassigned from it.labeled
/unlabeled
: This lets you know when labels are added to or removed from a pull request.edited
: This event occurs if someone changes the pull request’s title or description.
We can use the types
keyword to tell GitHub Actions which of these event types we care about. For instance, if we only want our workflow to run when a pull request gets labeled or unlabeled, here’s what we could use:
|
|
Or, if we want to be really thorough and trigger our workflow on all the event types we’ve talked about, we would use something like this:
|
|
Just remember, every pull_request
event will always have one of these types attached to it. It’s like a little tag that tells us what happened. The types
keyword helps us pick and choose which types we want our workflow to respond to. And if we don’t specify any types, like in our first example, GitHub Actions will just assume we mean opened
, reopened
, and synchronize
.
Fine-Tuning Your pull_request
Triggers: Branches and Paths
Similar to how we used filters with the push
event, we can fine-tune our pull_request
triggers using branches
, branches-ignore
, paths
, and paths-ignore
. Here’s the key difference when using them with pull_request
events:
Understanding the Pull Request Process
Remember those branches we talked about earlier - main
and our release
branches? When developers want to contribute changes, they typically follow these steps:
- Create a Branch: They start by branching off from their target branch (like
main
or arelease
branch). This new branch - called the source (or head) branch - gives them a safe space to work on their changes. - Make Changes: They make their code changes and commit them to their source branch.
- Open a Pull Request: They create a pull request on GitHub, connecting their source branch (with the new changes) to the target (or base) branch (where they want their changes to end up).
- Review and Merge: Other developers review the code, potentially suggesting changes. Once everything’s approved, the pull request gets merged - which means all the changes from the source branch become part of the target branch.
Filtering Based on Branches
Here are a couple of examples:
Example 1: Triggering only on pull requests targeting the main
branch:
|
|
This setup is straightforward. Our workflow will only run when someone creates or updates a pull request that’s aimed at merging changes into the main
branch.
Example 2: Triggering on pull requests targeting branches starting with release/
, except for release/old-version
:
|
|
Here’s a slightly more complex scenario: Our workflow will run when the pull request is meant to be merged into any branch that starts with release/
, unless that branch is named exactly release/old-version
.
Filtering Based on File Paths
Remember, a pull request is always about merging changes from one branch into another. While branch filters look at where your changes are going, paths
and paths-ignore
look at what you’re actually changing in your source branch.
Let’s say someone’s working on a feature branch. If they’re only updating documentation, do we need to run our full test suite? Probably not. But if they’re modifying our JavaScript files? That’s when we want our tests to kick in.
Here’s how we handle that:
|
|
This workflow says: “If someone changes any file in the src
directory or any JavaScript file - except documentation - we need to run our checks.”
Combining Branch and Path Filters
But here’s where it gets really powerful - we can combine both filters! Let’s say we want to be extra careful with code going into our main
branch:
|
|
Now our workflow only runs when someone’s trying to merge JavaScript or source code changes into the main
branch. Documentation updates or changes to other branches? We might ignore them or handle those differently.
Let’s Practice!
Alright, let’s apply what we’ve learned! We’ll create a workflow that demonstrates how to use pull request filters effectively.
Goal: We want to create a workflow that only triggers under two specific conditions:
- When someone opens a pull request targeting our
main
branch - When that pull request includes changes to JavaScript files in our
src
directory
Here’s the Plan: We’ll walk through this process step by step:
- Create a new branch to work on our changes
- Set up a
src
directory and add a JavaScript file to it - Update our workflow file to use pull request filters
- Push our changes and open a pull request
- Observe how our workflow responds to the pull request
Note that we won’t actually merge our changes - we’re just interested in seeing how the workflow responds when we open the pull request.
Steps:
- Create a New Branch
First, let’s create a new branch called test-pr-filters
. In Git, branches let us work on changes separately from our main code, and it’s good practice to give them descriptive names - in our case, we’re testing pull request filters.
|
|
Git Branch Commands: You might see different commands for working with branches in Git:
git branch <name>
creates a branchgit checkout <name>
switches to a branchgit checkout -b <name>
does both in one step (what we used above)git switch -c <name>
-c
(short for--create
) is the newer (Git 2.23+) it also does both in one step but in a more intuitive wayWe used
checkout -b
since you’ll often see it in documentation, but feel free to useswitch -c
if you prefer.
If you’re using VS Code, you can also create branches through the interface - check our previous article Setting Up Your First GitHub Repository for step-by-step instructions.
- Set Up the Source Directory
Now we’ll create a src
directory and add a JavaScript file to it. Many projects keep their source code in a src
directory - it’s a common convention that helps organize code files.
|
|
Quick Note: We’re using mkdir to create a directory and echo with > to create and write to our file. If you prefer, you can also:
- Create the
src
folder manually in your file explorer- Create
test.js
in your favorite code editor- Copy our existing
test.js
file into the newsrc
directory
The important thing is that we end up with a JavaScript file inside a src
directory, as this matches the path filter we’ll set up in our workflow.
If you’re using VS Code, you can right-click in the explorer panel to create both the directory and file - refer to our previous article for the detailed steps.
- Update the Workflow File
Let’s modify our workflow file to use pull request filters. We’ll update main.yml
in the .github/workflows
directory to run only when someone opens a pull request targeting our main
branch with changes to JavaScript files in the src
directory.
|
|
About the Workflow:
- The
pull_request
trigger responds to pull request activitiesbranches: [main]
ensures we only run on PRs targeting mainpaths: ['src/**.js']
matches any .js file in the src directory- The
*
means “any subdirectory” (so it would also matchsrc/utils/file.js
)- Our job simply prints a message confirming why it ran
This workflow will help us verify that our filters are working correctly when we open our pull request.
- Push Changes
At this point, the new branch exists only on your local machine.
For GitHub (or any remote repository), both branches need to be available on GitHub before merging so that GitHub can see and compare them.
To merge our local branch test-pr-filters
with the main
branch, we first need to push it to GitHub.
Pushing the branch does not merge it with main
—it simply makes it available remotely.
This way, when we open a pull request later, GitHub will be able to compare both branches properly.
|
|
About These Commands:
git add .
stages all our changes (the new src directory and updated workflow file)git commit -m "..."
creates a commit with a descriptive message.git push -u origin test-pr-filters
→- The
-u
flag (short for--set-upstream
) tells Git to remember which remote branch to push to in the future.- This way, for future
git push
commands, you don’t have to specify the remote branch every time—it will automatically push totest-pr-filters
on GitHub.
- Open a Pull Request
Now that we’ve pushed our branch to GitHub, let’s open a pull request to see our workflow filters in action.
When you go to your repository on GitHub, you should see a “Compare & pull request” button, as shown below:
This appears because GitHub notices your newly pushed branch.
Steps to Open a Pull Request:
-
Click the Compare & pull request button and you’ll see a form to create your pull request, like this:
-
In the pull request form:
-
Set Base branch: This is the branch where we want to merge our changes (
main
). -
Set Compare branch: This is the branch with the changes we’ve made (
test-pr-filters
). -
Title: GitHub automatically uses the most recent commit message as the title for the pull request. You can leave it as is or modify it for clarity.
-
Description: Provide a brief explanation of the purpose of the pull request. For example:
“This pull request tests the GitHub Actions workflow trigger by creating a pull request that targets the main branch. The pull request includes changes to a JavaScript file in the src directory and serves to verify whether the workflow responds to pull requests with these criteria.”
-
-
Click the green “Create pull request” button.
Note:
Don’t merge the pull request yet. At this point, we want to observe whether the pull request successfully triggers our GitHub Actions workflow. We’ll merge the pull request in a later step.
If you prefer using VS Code’s interface for pushing changes and creating pull requests, refer to our previous guide Setting Up Your First GitHub Repository for a step-by-step walkthrough.
- Observe the Workflow Results
After creating the pull request, navigate to the “Actions” tab in your GitHub repository. You should see the “PR Filter Demo” workflow running, as shown below:
Click on the workflow run to view the details. If the workflow is configured correctly, you should see an output similar to this:
|
|
Why This Happened:
- The pull request targets the
main
branch, as specified in our workflow trigger. - Changes were made to JavaScript files in the
src
directory, satisfying thepaths
condition.
Further Testing the Filters
To verify the robustness of your filters, try these scenarios:
- Create a pull request targeting a different branch: The workflow shouldn’t run.
- Modify non-JavaScript files in the
src
directory: The workflow shouldn’t run. - Change files outside the
src
directory: The workflow shouldn’t run.
Each of these cases confirms that our workflow responds only when both conditions are met.
- Merge the Pull Request
After confirming that the workflow triggered successfully, you can now merge the pull request to clean up your repository and leave it in a good state for future use.
Pull Request Overview
When viewing the pull request, you’ll see a summary similar to this:
What This Means:
- “All checks passed”: This confirms that our GitHub Actions workflow ran successfully without issues.
- “This branch has no conflicts with the base branch”: Indicates that
test-pr-filters
can be merged intomain
automatically.
Steps to Merge:
- Click the green “Merge pull request” button.
- Confirm the merge when prompted.
- Delete the
test-pr-filters
branch if prompted, as it is no longer needed.
Why We’re Merging:
Although the goal of this guide was to test the workflow trigger, merging the branches ensures the repository remains organized. Future articles and readers following along won’t encounter multiple stale branches.
Recap
🎉 Congratulations! You’ve successfully developed a strong understanding of two of the most commonly used triggers in GitHub Actions: push
and pull_request
. This sets a solid foundation for automating and improving your GitHub workflows.
In this guide, we’ve:
- Explored the
push
andpull_request
triggers, understanding how they work and when to use them. - Fine-tuned workflows with branches and path filters, giving you precise control over when your workflows run.
- Successfully set up workflows that respond to both push and pull requests, targeting specific branches and files.
These triggers lay the foundation for more advanced tasks, such as code reviews, testing, and deployments, all built on the same principles.
But this is just the beginning! 🚀 In our next article, we’ll dive into two more advanced triggers:
- Scheduled triggers for running workflows at specific times.
- Manual triggers for workflows you can launch on demand.
Until then, take some time to experiment with the branches and path filters we’ve covered here. You’ll discover just how much control and flexibility they provide for automating your GitHub projects.