The efficiency of a Continuous Integration and Continuous Deployment (CI/CD) pipeline is often measured by its ability to execute only the necessary tasks. In modern software architecture, particularly with monorepos, running a full suite of integration tests or deployments for every minor commit is a waste of computational resources and time. Implementing file-change detection allows developers to create conditional execution paths, ensuring that slow tasks are triggered only when the relevant components of the codebase are modified.
Mechanisms for Detecting File Changes
GitHub Actions provide several specialized tools to identify modified files relative to target branches, previous commits, or specific tags. The primary objective is to determine what has been added, removed, or modified to drive subsequent workflow logic.
The tj-actions/changed-files action is a robust solution for this requirement. It is designed to scale for large repositories and supports Git submodules and merge queues for pull requests. To achieve high performance, it leverages either GitHub's REST API or native Git diff commands, typically resulting in execution times between 0 and 10 seconds.
These tools are compatible with a wide range of events, including:
- pull_request*
- push
- merge_group
- release
It is critical to note that these actions identify files changed by the event that triggered the workflow; they cannot detect pending uncommitted changes created during the actual execution of the workflow.
Technical Implementation of tj-actions/changed-files
The tj-actions/changed-files action provides a highly configurable interface for filtering and outputting changes. It follows Semantic Versioning (SemVer), where major versions indicate breaking changes, minor versions introduce backward-compatible features, and patches address bug fixes.
Configuration and Inputs
The action allows for precise control over which files are tracked through the files input. Users can provide a list of patterns to include or exclude files.
yaml
- name: Get changed files
id: changed-files-specific
uses: tj-actions/[email protected]
with:
files: |
my-file.txt
*.sh
*.png
!*.md
test_directory/**
**/*.sql
Beyond simple file lists, the action supports advanced configurations:
- safe_output: When set to false, it stops escaping unsafe filename characters, which is useful when storing outputs in environment variables to prevent command injection.
- separator: Allows customization of the output delimiter (e.g., using a comma , instead of the default).
- path: Enables the action to run within a specific directory if the repository is checked out into a sub-folder.
Output Handling and Data Persistence
Data can be retrieved via step outputs or written directly to the filesystem for downstream processing.
| Output Type | Description | Format/Detail |
|---|---|---|
all_changed_files |
List of all modified files | String/JSON |
added_files |
Files newly created in the commit | String/JSON |
any_changed |
Boolean indicator if any files matched | true or false |
unmerged_files_count |
Number of files with merge conflicts | String |
If write_output_files is set to true, the action creates files in .github/outputs/. The format is determined by the json input; if json: true, a .json file is created. If a custom separator like , is used, a .csv file is generated; otherwise, the output defaults to .txt.
Conditional Workflow Execution
The primary value of file-change detection is the ability to use if conditionals to skip unnecessary steps. This is particularly effective for "slow tasks" such as heavy integration tests.
Practical Implementation Examples
To execute a step only when files in a specific directory (e.g., .github/) are changed:
```yaml
- name: Get changed files in the .github folder
id: changed-files-specific
uses: tj-actions/[email protected]
with:
files: .github/**
- name: Run step if any file(s) in the .github folder change
if: steps.changed-files-specific.outputs.any_changed == 'true'
run: |
echo "One or more files in the .github folder has changed."
```
For release workflows triggered by tags, the action can compare the current tag against the previous one:
```yaml
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get changed files
id: changed-files
uses: tj-actions/[email protected]
```
Comparison of Specialized Change Actions
While tj-actions/changed-files provides comprehensive depth and file listing, other actions like dorny/paths-filter and file-changes-action offer alternative approaches to conditional logic.
dorny/paths-filter: Focused specifically on enabling conditional execution of jobs and steps based on modified paths. It is highly optimized for monorepos to save time and resources.file-changes-action: Focuses on outputting variables and files that explicitly categorize changes as added, removed, or modified. It provides specific inputs for custom GitHub tokens and SHA comparisons (pushBeforeandpushAfter) to define the exact range of the diff.
Conclusion
Integrating file-change detection into GitHub Actions transforms a static pipeline into a dynamic, context-aware system. By leveraging tools like tj-actions/changed-files, teams can significantly reduce CI cycle times and compute costs. The ability to filter by glob patterns, output results in machine-readable JSON, and trigger jobs based on specific directory changes is essential for maintaining developer productivity in complex, multi-module repositories. As repositories grow, the shift from "test everything" to "test only what changed" becomes a critical requirement for scalable software delivery.