How Sparse Checkout Works Internally in actions/checkout
The actions/checkout action implements sparse checkout by parsing the sparse-checkout input into path patterns, validating Git version 2.28+, fetching with --filter=blob:none for partial clones, and executing either git sparse-checkout set for cone mode or manually writing patterns to .git/info/sparse-checkout for legacy non-cone mode.
The actions/checkout GitHub Action supports sparse checkout to reduce repository size and clone time by fetching only specific directories. This feature leverages Git's native sparse-checkout capabilities according to the source code in the actions/checkout repository. Internally, the action orchestrates a series of Git commands across multiple TypeScript modules to configure the repository before the final checkout operation.
Input Parsing and Settings Configuration
The sparse checkout process begins in src/input-helper.ts, where the action reads the multiline sparse-checkout input and the optional sparse-checkout-cone-mode flag. The multiline input splits on line breaks, with each line becoming a path pattern stored in the settings object.
const sparseCheckout = core.getMultilineInput('sparse-checkout')
if (sparseCheckout.length) {
result.sparseCheckout = sparseCheckout
}
result.sparseCheckoutConeMode =
(core.getInput('sparse-checkout-cone-mode') || 'true').toUpperCase() === 'TRUE'
These values populate the IGitSourceSettings interface defined in src/git-source-settings.ts, which includes sparseCheckout: string[] and sparseCheckoutConeMode: boolean. This configuration object passes downstream to the source provider.
Git Version Validation
Before executing sparse checkout commands, the action validates the Git version in src/git-command-manager.ts. The MinimumGitSparseCheckoutVersion constant requires Git 2.28 or later.
if (this.doSparseCheckout) {
if (!this.gitVersion.checkMinimum(MinimumGitSparseCheckoutVersion)) {
throw new Error(
`Minimum Git version required for sparse checkout is ${MinimumGitSparseCheckoutVersion}`
)
}
}
If the runner's Git version is older than 2.28, the action aborts with a clear error message preventing incompatible operations.
Partial Clone Optimization
When sparse checkout is enabled, the fetch operation in src/git-source-provider.ts adds --filter=blob:none to create a partial clone. This optimization ensures Git only pulls tree objects initially, leaving actual file blobs to be fetched lazily when accessed.
if (settings.sparseCheckout) {
fetchOptions.filter = 'blob:none'
}
await git.fetch(refSpec, fetchOptions)
This approach significantly reduces initial clone size and network transfer for large repositories.
Cone Mode vs Non-Cone Mode Implementation
The src/git-source-provider.ts file determines which sparse checkout strategy to apply based on the sparseCheckoutConeMode setting. The action supports three distinct paths:
No Sparse Checkout → Calls git.disableSparseCheckout() to clear any previous configuration.
Cone Mode (sparse-checkout-cone-mode: true) → Invokes git.sparseCheckout(patterns) which executes:
async sparseCheckout(sparseCheckout: string[]): Promise<void> {
await this.execGit(['sparse-checkout', 'set', ...sparseCheckout])
}
Git 2.28+ supports the cone algorithm, which is faster and automatically expands directory patterns.
Non-Cone Mode (sparse-checkout-cone-mode: false) → Invokes git.sparseCheckoutNonConeMode(patterns) which manually configures the sparse-checkout file:
async sparseCheckoutNonConeMode(sparseCheckout: string[]): Promise<void> {
await this.execGit(['config', 'core.sparseCheckout', 'true'])
const output = await this.execGit(['rev-parse','--git-path','info/sparse-checkout'])
const sparseCheckoutPath = path.join(this.workingDirectory, output.stdout.trimRight())
await fs.promises.appendFile(
sparseCheckoutPath,
`\n${sparseCheckout.join('\n')}\n`
)
}
This legacy method writes patterns directly to .git/info/sparse-checkout and enables the core.sparseCheckout config flag, supporting older Git versions that predate the sparse-checkout subcommand.
Disabling Sparse Checkout
When the sparse-checkout input is omitted, the action ensures a clean state by calling disableSparseCheckout() in src/git-command-manager.ts:
async disableSparseCheckout(): Promise<void> {
await this.execGit(['sparse-checkout', 'disable'])
await this.tryConfigUnset('extensions.worktreeConfig', false)
}
This removes any existing sparse-checkout configuration and cleans up worktree extensions.
Verification and Testing
The implementation is guarded by shell scripts in the __test__/ directory. The verify-sparse-checkout.sh script validates that the sparse-checkout list is non-empty, that all listed directories exist, and that only requested directories populate the working tree. Similarly, verify-sparse-checkout-non-cone-mode.sh verifies correct behavior when cone mode is disabled.
Practical Configuration Examples
Basic Sparse Checkout (Cone Mode)
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
src/
docs/
.github/
This configuration populates only the src/, docs/, and .github/ directories, leaving the rest as a partial clone.
Non-Cone Mode (Legacy)
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
lib/
test/
sparse-checkout-cone-mode: false
This forces file-based sparse checkout by writing patterns to .git/info/sparse-checkout and setting core.sparseCheckout=true.
Combined with Shallow Fetch
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
sparse-checkout: |
.github/
.eslintrc.js
The fetch step uses --filter=blob:none alongside shallow cloning to minimize repository size.
Summary
- Input parsing in
src/input-helper.tsextracts path patterns from the multilinesparse-checkoutinput and determines cone mode preference. - Version validation ensures Git 2.28+ is installed before attempting sparse checkout operations.
- Partial clone optimization uses
--filter=blob:noneduring fetch to defer blob downloads until actually needed. - Cone mode executes
git sparse-checkout setfor efficient directory-based filtering. - Non-cone mode manually writes to
.git/info/sparse-checkoutfor legacy compatibility. - Cleanup via
disableSparseCheckout()clears configurations when sparse checkout is not requested.
Frequently Asked Questions
What is the minimum Git version required for sparse checkout in actions/checkout?
Git version 2.28 is the minimum required version. The action checks this via MinimumGitSparseCheckoutVersion in src/git-command-manager.ts and throws an error if the runner's Git version is insufficient.
What is the difference between cone mode and non-cone mode in sparse checkout?
Cone mode (default) uses git sparse-checkout set and implements an algorithm that automatically expands directory patterns, offering better performance for typical directory-based filtering. Non-cone mode manually writes patterns to .git/info/sparse-checkout and sets core.sparseCheckout=true, supporting legacy Git versions and complex pattern matching but with slower performance.
How does actions/checkout optimize fetch performance when using sparse checkout?
The action automatically adds --filter=blob:none to the fetch command when sparse checkout is enabled. This creates a partial clone where only tree objects are fetched initially, and file blobs are downloaded on-demand when accessed, significantly reducing initial clone time and storage.
Can sparse checkout be combined with shallow cloning?
Yes. You can combine sparse-checkout with fetch-depth: 1 (or any shallow depth) to create a minimal repository footprint. The action will apply both optimizations: shallow history via --depth and partial file retrieval via --filter=blob:none.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →