Skip to content

Git Sparse Checkout: Work with Part of a Repository

Lesson 3 of 11Intermediate → Advanced11 min readModern Git Workflows · Modern Git ProductivityVerified: Git 2.43.0 on Ubuntu 24.04; behaviour checked against the current git-sparse-checkout documentation

Sparse checkout limits which tracked paths are written into your working tree. The repository still contains everything; your directory contains a subset.

The critical distinction, and the reason this feature is so often misunderstood:

Sparse checkout controls which tracked paths populate the working tree. It is not the same thing as downloading less of the repository.

Every object is still in .git. If you want to transfer less data, that is partial clone or shallow clone — different features that combine well with this one.

Everything is present; only some paths are checked out

Two panels. The left panel, the repository object database, contains objects for every path: README, services slash api, services slash web, libs slash shared and docs. The right panel, the working tree, contains only README and services slash api. An arrow between them is labelled sparse checkout, indicating it filters which paths are written to disk while all objects remain available.

.git — all objects presentworking tree — on diskREADME.mdservices/api/services/web/libs/shared/docs/README.mdservices/api/(not written to disk,but fully available)sparse checkoutgit cat-file -p HEAD:services/web/file.txt still works

You can prove the distinction in one command. With services/web excluded from the working tree:

Terminal window
git cat-file -p HEAD:services/web/file.txt
content of services/web

The file is not on disk, and its content is immediately available — because the object was downloaded like everything else.

Large monorepos. A repository with two hundred services where you work on one. Checking out the whole tree costs disk, and makes git status, editor indexing and file searches slower.

Repositories with large asset directories. Design files, test fixtures, sample data.

Focused CI jobs. A job that only builds one component does not need the rest checked out.

Reducing tool noise. Editor search, language servers and file watchers all scale with the number of files on disk.

When it does not help: a repository that clones in seconds. Sparse checkout is configuration to maintain, and on a small repository it buys nothing.

Two commands.

Terminal window
git sparse-checkout set services/api

What it doesEnables sparse checkout and sets the list of directories to populate, then updates the working tree to match.

Why we run itThis is the single command that configures the feature. Cone mode is the default, so directories are all you need to specify.

Expected resultNo output. The working tree is rewritten to contain only the named directories plus files at the repository root.

On an existing clone, that is all:

Terminal window
git sparse-checkout set services/api
find . -path ./.git -prune -o -type f -print
./README.md
./services/api/file.txt

Note that README.md appears even though you did not ask for it. In cone mode, files at the root of the repository are always included, as are files in the parent directories of anything you selected. That is deliberate: the top-level files are usually configuration and documentation you want.

For a fresh clone, skip the initial checkout so the full tree is never written to disk:

Terminal window
git clone --no-checkout <url> project
cd project
git sparse-checkout set services/api
git checkout main
Terminal window
git sparse-checkout list
services/api
Terminal window
git sparse-checkout add libs/shared
./README.md
./libs/shared/file.txt
./services/api/file.txt

add appends; set replaces the whole list. There is no remove subcommand — to drop a directory, run set again with the paths you want to keep.

To turn it off entirely and restore the full tree:

Terminal window
git sparse-checkout disable

reapply re-enforces the rules after an operation has materialised paths that should be excluded:

Terminal window
git sparse-checkout reapply

You need this occasionally after a merge or a checkout brings files back that your rules exclude.

Cone mode is the default and the mode you should use. You specify directories; Git includes everything beneath them, plus the files in each ancestor directory along the way.

Terminal window
git sparse-checkout set services/api libs/shared

That gives you:

  • Every file under services/api/
  • Every file under libs/shared/
  • Files directly in services/ and libs/
  • Files at the repository root

The name comes from the shape: a cone widening from the root down to the directories you selected.

The older mode accepted arbitrary gitignore-style patterns, including negations:

Terminal window
git sparse-checkout set --no-cone '/*' '!unwanted'

It is deprecated, and the reasons are practical rather than stylistic: pattern matching scales poorly with the number of files, it is incompatible with the sparse index, shell glob expansion causes surprises, and there is no way to undo an accidental addition.

By default the index still lists every file in the repository, even those not checked out. On a very large repository, that index is itself large enough to slow down git status and git add.

The sparse index shrinks it, collapsing excluded directories into single entries:

Terminal window
git sparse-checkout set --sparse-index services/api

This is where the real performance benefit lives on large monorepos — the working tree shrinking is useful, but the index shrinking is what makes everyday commands fast again.

This is the section worth reading twice, because these four are constantly conflated.

FeatureControlsObjects downloadedWorking tree
Sparse checkoutWhich paths appear on diskAll of themSubset
Partial cloneWhich objects arrive up frontFewer; more on demandFull
Shallow cloneHow much history arrivesFewer commitsFull
WorktreesNothing — adds working treesAllOne per worktree

Sparse checkout does not reduce clone size. Every object still transfers.

Partial clone does not hide files. Everything is checked out; the objects are fetched when needed.

They combine, and the combination is the standard approach on a large monorepo:

Terminal window
git clone --filter=blob:none --no-checkout <url> project
cd project
git sparse-checkout set services/api
git checkout main

Now: the clone transferred no file contents up front (partial clone), and only services/api is written to disk (sparse checkout). Blobs for the paths you checked out are fetched during checkout; blobs for everything else are never fetched unless you ask for them.

The setup most teams on a large monorepo converge on, start to finish.

  1. Clone without checking out, and without file contents:

    Terminal window
    git clone --filter=blob:none --no-checkout <url> monorepo
    cd monorepo
  2. Select the paths you need, with the sparse index enabled:

    Terminal window
    git sparse-checkout set --sparse-index services/api libs/shared

    Include shared libraries your service depends on — a build that cannot see libs/shared will fail in a way that is not obviously a checkout problem.

  3. Check out:

    Terminal window
    git checkout main

    Only the selected paths are written, and only their blobs are fetched.

  4. Verify the build works before adopting this team-wide. This is the step people skip, and build systems that expect the whole tree are the most common obstacle.

Adding a dependency later is one command:

Terminal window
git sparse-checkout add libs/auth

What actually gets faster, and by how much, depends on which bottleneck you had.

OperationEffect of sparse checkout
git cloneNone — combine with partial clone
git checkout / switchFaster; fewer files written
git statusFaster with --sparse-index; modest without
git addFaster with --sparse-index
git log, git grepUnchanged — they cover the whole repository
Editor indexingSubstantially faster; far fewer files
Disk usageWorking tree only; .git unchanged

The two rows worth internalising are the first and the fifth. Sparse checkout is not a clone optimisation, and it does not narrow commands that operate on history.

The gain most people actually notice is not Git at all — it is their editor, language server and file watcher no longer scanning a million files.

Sparse checkout configuration is per worktree, stored in that worktree’s own config.worktree file rather than the shared config.

That makes a useful pattern possible on a monorepo: one worktree per service, each containing only that service’s paths, all sharing a single object database.

Terminal window
git worktree add ../api main
cd ../api && git sparse-checkout set services/api
git worktree add ../web -b web-work main
cd ../web && git sparse-checkout set services/web

Each directory is small and focused; the history is stored once.

Two commands answer “why is this file here?” or “why is it not?”.

Terminal window
git sparse-checkout check-rules

Reads paths on standard input and prints those the current rules would include:

Terminal window
printf 'services/api/main.py\ndocs/index.md\n' | git sparse-checkout check-rules

This is the fast way to test a rule set before applying it, particularly when debugging a directory you expected to appear.

The index also records which entries are skipped. Files excluded by sparse checkout are marked with the skip-worktree bit:

Terminal window
git ls-files -v | grep '^S' | head
S docs/file.txt
S libs/shared/file.txt
S services/web/file.txt

A capital S means the entry is present in the index but deliberately not written to disk. This is how git status knows not to report those files as deleted — which is the mechanism underneath the whole feature.

Commands still operate on everything. git log, git grep and git diff cover the whole repository by default, not just your checked-out paths. Restrict them by path if you want otherwise:

Terminal window
git log -- services/api

A commit can touch paths you cannot see. Merging a branch that changes services/web succeeds and updates the index, without writing anything to your disk. That is correct, and occasionally disorienting.

Not a security boundary. Excluding a path does not restrict access to it. Anyone with the repository has every object. If you need genuine access control, split the repository.

Some tooling assumes a full tree. Build systems that expect every module present, or scripts that walk directories, may fail. This is the most common practical obstacle.

reapply is sometimes needed after operations that materialise excluded paths.

Changing the path list rewrites the working tree. Uncommitted changes in a directory you remove from the list can be lost — commit or stash before changing the set.

“Sparse checkout will make my clone smaller.” It will not. It controls the working tree only.

Following a tutorial that uses init. Deprecated. Use set.

Hand-editing .git/info/sparse-checkout. Bypasses the config set manages.

Using --no-cone for a directory list. Cone mode does directories, faster and safely.

Expecting remove. There is no such subcommand. Run set again with the paths you want.

Assuming your tools cope. Test the build in a sparse checkout before adopting it team-wide.

Treating it as access control. Every object is present.

Forgetting add versus set. add appends, set replaces. Using set when you meant add silently narrows your checkout.

A few behaviours to expect once it is set up.

Switching branches works normally. Sparsity is a property of your working tree, not of the branch. A branch that adds files under an excluded path will not materialise them, and that is correct.

A file appearing unexpectedly usually means an operation materialised it — some merges and checkouts do. Re-enforce the rules:

Terminal window
git sparse-checkout reapply

A file you need is missing. Add its directory:

Terminal window
git sparse-checkout add libs/auth

Your build fails on a path you cannot see. The most common obstacle. Build systems that walk the whole tree, or resolve a dependency by relative path, need those paths present. Either add them, or accept that sparse checkout does not suit that project.

git status looks clean when you expected changes. Excluded files are marked skip-worktree in the index, so Git deliberately does not report them as deleted. Confirm with:

Terminal window
git ls-files -v | grep '^S' | head

Cleaning up leftover excluded files — occasionally a path is excluded while a stale copy remains on disk. Recent Git versions provide a subcommand for this:

Terminal window
git sparse-checkout clean

It is not available in every version. Check what your Git offers:

Terminal window
git sparse-checkout
usage: git sparse-checkout (init | list | set | add | reapply | disable | check-rules) [<options>]

If clean is absent — as it is in Git 2.43 — git sparse-checkout reapply handles most cases, and removing the stale file by hand handles the rest.

Sparse checkout is a filter on what gets written to disk, not on what you have.

The repository is a warehouse containing everything. Sparse checkout decides which shelves get unpacked onto your bench. Nothing leaves the warehouse, and you can ask for anything at any time — it simply is not laid out in front of you.

  • Sparse checkout limits which tracked paths populate the working tree; all objects remain present.
  • It does not reduce what is downloaded — that is partial or shallow clone.
  • Cone mode is the default; specify directories, not patterns. Non-cone mode is deprecated.
  • set replaces the list, add appends, list shows it, disable restores the full tree.
  • git sparse-checkout init is deprecated.
  • --sparse-index shrinks the index too, which is where large-repository performance improves.
  • Configuration is per worktree, enabling one focused worktree per component.
  • Commands still operate on the whole repository; restrict by path if needed.
  1. Build a small monorepo:

    Terminal window
    mkdir ~/sparse-lab && cd ~/sparse-lab && git init
    for d in services/api services/web libs/shared docs; do
    mkdir -p $d && echo "content of $d" > $d/file.txt
    done
    echo "root readme" > README.md
    git add . && git commit -m "Initial monorepo"
  2. Confirm everything is present: find . -path ./.git -prune -o -type f -print | sort.

  3. Narrow it:

    Terminal window
    git sparse-checkout set services/api
    find . -path ./.git -prune -o -type f -print | sort

    Predict first: will README.md still be there?

  4. Prove the objects are still available, even though the file is gone from disk:

    Terminal window
    git cat-file -p HEAD:services/web/file.txt
  5. Confirm the file is still tracked:

    Terminal window
    git ls-tree -r --name-only HEAD
  6. Add a second directory: git sparse-checkout add libs/shared, then check the tree again.

  7. Inspect the config: git sparse-checkout list and git config --get core.sparseCheckoutCone.

  8. Restore everything: git sparse-checkout disable.

Steps 4 and 5 are the whole lesson: the file is absent from disk, present in the repository, and still tracked. That is what “controls the working tree, not the download” means concretely.

Sparse checkout controls what lands on disk. Partial clone controls what arrives over the network.