Baeldung Pro – Ops – NPI EA (cat = Baeldung on Ops)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

Partner – Orkes – NPI EA (cat=Kubernetes)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Introduction

GitHub Actions provides a flexible and powerful platform for automating workflows within a GitHub repository. One common use case in continuous integration (CI) workflows is the ability to detect which files have changed in a pull request (PR) or push event.

In this article, we’ll explore how to detect changed files in a PR and on push using GitHub Actions. We’ll also demonstrate how to combine both scenarios into a single workflow, ensuring that file detection logic works reliably for both event types.

2. Detecting Changed Files in a Pull Request

When a PR is opened or updated, GitHub triggers a pull_request event. This event provides access to two important pieces of information: The base branch and the head branch. By comparing these two branches, we can detect which files have changed. This is especially useful when we want to run a specific job, like tests or linters, only for the parts of the codebase that were modified in the PR.

Let’s begin with a pull_request workflow that checks out the code and uses git diff to detect modified files:

name: Detect PR Changes

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout the code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Ensures we have the full history for diff

      - name: Get changed files
        id: changes
        run: |
          git fetch origin ${{ github.base_ref }}
          CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
          echo "changed=$CHANGED_FILES" >> $GITHUB_OUTPUT

      - name: Print changed files
        run: echo "Changed files: ${{ steps.changes.outputs.changed }}"

The workflow is configured to trigger on pull_request events, specifically for actions such as opened, synchronize, and reopened. Within the job, in the first step, we use the actions/checkout@v4 action with fetch-depth: 0 to ensure that we retrieve the full commit history before comparing the difference.

Next, we fetch the base branch of the PR using github.base_ref in the second step. Then, we compare it against the current HEAD using git diff –name-only. This triple-dot syntax allows us to see the complete set of files that have changed between the branches. Finally, we store the result in an output variable so that subsequent steps in the workflow can access the list of changed files.

Lastly, we use echo to print the result and verify the output during the workflow execution.

3. Detecting Changed Files on Push

While PRs are common for collaborative development, direct pushes to branches are still relevant in many workflows. As another option, we can detect changed files when pushing new code to our branches. When using the push event, GitHub Actions provides access to the commit SHA before and after the push.

This approach is straightforward and effective for detecting changes in workflows triggered by direct branch updates. It’s especially useful for scenarios like production deployments, validations, or testing workflows that need to respond to updates.

We can use these SHAs to detect changes:

name: Detect Push Changes

on:
  push:
    branches:
      - main
      - develop

jobs:
  detect-push-changes:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout the code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get changed files on push
        id: push_changes
        run: |
          git fetch origin ${{ github.ref_name }}
          CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }})
          echo "changed=$CHANGED_FILES" >> $GITHUB_OUTPUT

      - name: Print changed files
        run: echo "Changed files: ${{ steps.push_changes.outputs.changed }}"

As discussed, GitHub’s push event provides two important commit SHAs: before and after, representing the state of the branch before and after the push. By using git diff with these two SHAs, we can determine exactly which files were modified.

Similar to the pull request approach, we retrieve the full commit history using the actions/checkout@v4 action with fetch-depth: 0.

In the second step of the workflow, we compare the before and after SHAs using git diff to get a list of changed files.

Finally, we print the result to verify our changes during the workflow execution.

4. Combining Pull Requests and Pushes on a Single Workflow

In practice, we may want a unified workflow that handles both PRs and pushes. Rather than duplicating logic across separate workflows, we can combine support for both event types into a single, unified GitHub workflow.

We can achieve this by adding conditional logic based on the event_name value:

name: Detect File Changes

on:
  push:
    branches:
      - main
      - develop
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  detect-changed-files:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Determine event type
        id: event_type
        run: echo "Event is ${{ github.event_name }}"

      - name: Get changed files (PR)
        if: github.event_name == 'pull_request'
        id: pr_changes
        run: |
          git fetch origin ${{ github.base_ref }}
          CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
          echo "changed=$CHANGED_FILES" >> $GITHUB_OUTPUT

      - name: Get changed files (Push)
        if: github.event_name == 'push'
        id: push_changes
        run: |
          git fetch origin ${{ github.ref_name }}
          CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }})
          echo "changed=$CHANGED_FILES" >> $GITHUB_OUTPUT

      - name: Print changed files
        run: |
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            echo "Changed files: ${{ steps.pr_changes.outputs.changed }}"
          else
            echo "Changed files: ${{ steps.push_changes.outputs.changed }}"
          fi

This workflow listens to both push and pull_request events, allowing it to handle file change detection in a unified way. It begins by checking out the code with full history using actions/checkoutv4, ensuring that all commits needed for diff comparisons are available.

Depending on the event type, determined by github.event_name, the workflow executes the appropriate set of steps: for pull requests, it compares the base branch to the head using git diff, while for pushes, it compares the before and after commit SHAs. Then, we store each result in an output variable. A final step prints the list of changed files, using conditional logic to select the correct output.

This approach reduces maintenance overhead while also providing consistent file change detection, no matter how updates are made to the repository.

5. Conclusion

In this article, we explored how detecting file changes in GitHub Actions is a powerful technique that enables more intelligent and efficient CI/CD workflows.

Detecting file changes in GitHub Actions is a powerful technique that enables more intelligent and efficient CI/CD workflows. Whether we’re working with PRs, direct pushes, or both, we can tailor our workflow to respond only to relevant changes, either for specific events or a combination of them.

Subscribe
Notify of
guest
0 Comments
Oldest
Newest
Inline Feedbacks
View all comments