Sparse-checkout for large monorepos Jump to heading

A monorepo that has accumulated hundreds of packages and years of history can force every developer to check out tens of gigabytes to touch a single service. The full clone is slow, the working tree strains the editor’s file watcher, and a git status walks paths nobody on the team will ever open. Git’s sparse-checkout, partial clone, and shallow clone features solve this from three angles at once — history depth, blob transfer, and working-tree population — and they compose cleanly. This page is a focused recipe within Monorepo Branch Topology, which covers how branch structure, ownership, and checkout strategy fit together at monorepo scale.

When to use this approach Jump to heading

Apply this recipe when:

  • A full clone of the repository exceeds a few gigabytes and most contributors only ever edit one or two directories.
  • Editor indexing, file watchers, or git status are visibly slow because the working tree contains far more files than any one task requires.
  • CI jobs spend a meaningful share of their wall-clock time cloning history and blobs the job will never read.
  • Different teams own disjoint slices of the tree — coordinated through Path-Based CODEOWNERS in a Monorepo — and rarely cross boundaries.
  • Your Git version is 2.30 or newer, so cone mode and the git sparse-checkout porcelain are available.

This recipe is not the right fit if your repository is small enough that a full clone completes in seconds, or if every build genuinely needs the entire tree present (some bundlers and language servers resolve cross-package references that break under a partial checkout). Measure before committing the team to it.

The diagram below shows how the three mechanisms stack: shallow clone truncates history, partial clone defers blobs, and sparse-checkout limits the populated paths.

Layered reductions from shallow clone, partial clone, and sparse-checkoutThree stacked bands narrowing from a full repository on the left to a minimal working tree on the right. Shallow clone removes deep history, partial clone removes unreferenced blobs, and sparse-checkout removes directories outside the cone, leaving only the packages the developer needs.Full repositoryall historyall blobsall paths--depthShallowrecent historyblob:nonePartialblobs on demandconeWorking treeonly yourpackages

Step-by-step recipe Jump to heading

Step 1 — Clone without blobs using a partial filter Jump to heading

Start by cloning history and trees but no file contents. The --filter=blob:none flag tells the server to omit every blob; Git fetches individual blobs lazily the first time a file is actually read. Pair it with --no-checkout so nothing is populated until the sparse set is defined.

# --filter=blob:none: defer all blob downloads until a file is read
# --no-checkout: do not populate the working tree yet
# --depth 1: (optional) truncate to the most recent commit only
git clone --filter=blob:none --no-checkout \
  https://github.com/acme/monorepo.git
cd monorepo

Verify the clone is partial and no working-tree files were written yet:

# Confirms a partial-clone filter is recorded on the origin remote
git config --get remote.origin.partialclonefilter
# Expected output: blob:none

# The working tree is empty apart from the .git directory
ls -A

Step 2 — Initialize cone-mode sparse-checkout Jump to heading

Enable sparse-checkout in cone mode. Cone mode matches whole directory prefixes using a fast hash lookup instead of walking gitignore-style patterns, which keeps the checkout predictable and quick even on a huge tree. Modern Git enables cone mode by default with git sparse-checkout init, but pass --cone explicitly for clarity.

# Enable sparse-checkout, restricting patterns to directory prefixes
git sparse-checkout init --cone

At this point only the top-level files (such as the root README, package.json, or build config) are eligible for checkout — no subdirectories are included yet.

Verify sparse mode is active and inspect the effective config:

# Lists the directories currently in the sparse set (empty except root)
git sparse-checkout list

# core.sparseCheckout and core.sparseCheckoutCone should both be true
git config core.sparseCheckout
git config core.sparseCheckoutCone

Step 3 — Set the directories you need Jump to heading

Declare the directory prefixes your work requires. git sparse-checkout set replaces the entire sparse set with the paths you list, then populates the working tree — fetching only the blobs for those paths thanks to the partial clone from Step 1.

# Populate exactly these directory trees (plus their parents' top-level files)
git sparse-checkout set services/checkout libs/payments

Cone mode always includes the files directly at the repository root and at each ancestor of a listed directory, so shared root-level tooling stays available.

Verify the working tree now contains only the requested subtrees and measure the size:

# Should list only the directories you set, plus root-level files
git sparse-checkout list

# Confirm nothing outside the sparse set was materialized
ls services/    # expected: only "checkout"

# Compare working-tree size against a full checkout
du -sh --exclude=.git .

Step 4 — Add more directories as work expands Jump to heading

When a task grows to touch another package, append to the sparse set with git sparse-checkout add rather than re-running set. add is additive and leaves your existing directories untouched.

# Add another tree without disturbing the current sparse set
git sparse-checkout add libs/shared-ui

To narrow the set back down later, run git sparse-checkout set again with the reduced list, or use git sparse-checkout reapply after editing .git/info/sparse-checkout if you manage it directly.

Verify the addition and re-measure:

# The new directory should now appear in the list and on disk
git sparse-checkout list
du -sh --exclude=.git libs/shared-ui

SAFETY WARNING: Narrowing the sparse set (via set, add in no-cone mode, or reapply) removes files from the working tree. If a path leaving the sparse set has uncommitted local changes, Git will refuse and warn — but --no-cone patterns can still silently drop tracked-but-modified files that fall outside the new set. Commit or stash first (git stash), and never edit .git/info/sparse-checkout by hand while you have dirty paths outside the cone. Recover an accidentally removed change with git stash pop, or restore a checked-in file with git restore <path> after widening the set again.

Step 5 — Optimize the checkout for CI Jump to heading

In CI you rarely need history or unused packages. Combine all three reductions for the fastest possible job checkout: shallow depth, blob filtering, and a single-directory sparse set. Setting sparse.expectFilesOutsideOfPatterns avoids surprises and --sparse on clone bootstraps the cone in one step.

# Fast CI checkout of a single package
git clone --filter=blob:none --depth 1 --sparse \
  https://github.com/acme/monorepo.git repo
cd repo

# --sparse leaves only root files checked out; scope to the build target
git sparse-checkout set services/checkout

# Fetch just the blobs the build reads; history stays shallow

For pipelines that key builds off which paths changed, align the checked-out cone with the trigger filter so a job only clones what its trigger scoped it to — a pattern covered in CI/CD Pipeline Trigger Mapping.

Verify the CI checkout is minimal:

# Depth is 1 (grafted history) and only the target tree exists
git rev-list --count HEAD          # expected: 1
git sparse-checkout list           # expected: services/checkout
du -sh --exclude=.git .

SAFETY WARNING: git sparse-checkout set --no-cone accepts arbitrary gitignore-style patterns and is far easier to misconfigure than cone mode — an over-broad negative pattern can remove large portions of your tree, including modified files, and no-cone is deprecated for interactive use. Prefer --cone. If you must use --no-cone, run git status first to confirm a clean tree, and keep a backup branch (git branch backup/pre-sparse) so you can git checkout backup/pre-sparse -- . to restore any path the pattern dropped.

Validation checklist Jump to heading

Before rolling this out to the team, confirm each item:

Frequently asked questions Jump to heading

Does sparse-checkout make the repository smaller on the server? Jump to heading

No. Sparse-checkout only affects which files are populated in your local working tree; the remote repository and its full history are unchanged, and every collaborator still sees the whole tree. What reduces network transfer is the partial clone (--filter=blob:none), which defers downloading blobs outside your sparse set until a file is actually read, and shallow clone (--depth), which truncates history. Sparse-checkout, partial clone, and shallow clone address three independent costs and are designed to be combined.

Can I use sparse-checkout with a shallow clone? Jump to heading

Yes, and in CI you usually should. --depth 1 truncates history to the latest commit, --filter=blob:none defers blob download, and git sparse-checkout set limits which paths are populated. The three are orthogonal: depth controls how far back history goes, the filter controls when blobs arrive, and the sparse set controls which directories land on disk. Together they produce the fastest checkout of a single package from an otherwise enormous monorepo.

What is the difference between cone mode and no-cone mode? Jump to heading

Cone mode restricts the sparse set to whole directory prefixes, which lets Git use a fast hash-based match and keeps the configuration predictable. No-cone mode accepts arbitrary gitignore-style patterns — more flexible, but slower, deprecated for interactive use, and easy to misconfigure into removing files that have local changes. Unless you have a specific need for per-file patterns, use --cone.