Git Sparse Checkout: Work with Part of a Repository
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.
What it actually does
Section titled “What it actually does”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.
You can prove the distinction in one command. With services/web excluded from the working tree:
git cat-file -p HEAD:services/web/file.txtcontent of services/webThe file is not on disk, and its content is immediately available — because the object was downloaded like everything else.
When it helps
Section titled “When it helps”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.
Setting it up
Section titled “Setting it up”Two commands.
git sparse-checkout set services/apiWhat 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:
git sparse-checkout set services/apifind . -path ./.git -prune -o -type f -print./README.md./services/api/file.txtNote 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:
git clone --no-checkout <url> projectcd projectgit sparse-checkout set services/apigit checkout mainManaging the path list
Section titled “Managing the path list”git sparse-checkout listservices/apigit sparse-checkout add libs/shared./README.md./libs/shared/file.txt./services/api/file.txtadd 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:
git sparse-checkout disablereapply re-enforces the rules after an operation has materialised paths that should be excluded:
git sparse-checkout reapplyYou need this occasionally after a merge or a checkout brings files back that your rules exclude.
Cone mode
Section titled “Cone mode”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.
git sparse-checkout set services/api libs/sharedThat gives you:
- Every file under
services/api/ - Every file under
libs/shared/ - Files directly in
services/andlibs/ - Files at the repository root
The name comes from the shape: a cone widening from the root down to the directories you selected.
Non-cone mode is deprecated
Section titled “Non-cone mode is deprecated”The older mode accepted arbitrary gitignore-style patterns, including negations:
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.
The sparse index
Section titled “The sparse index”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:
git sparse-checkout set --sparse-index services/apiThis 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.
How it relates to the other features
Section titled “How it relates to the other features”This is the section worth reading twice, because these four are constantly conflated.
| Feature | Controls | Objects downloaded | Working tree |
|---|---|---|---|
| Sparse checkout | Which paths appear on disk | All of them | Subset |
| Partial clone | Which objects arrive up front | Fewer; more on demand | Full |
| Shallow clone | How much history arrives | Fewer commits | Full |
| Worktrees | Nothing — adds working trees | All | One 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:
git clone --filter=blob:none --no-checkout <url> projectcd projectgit sparse-checkout set services/apigit checkout mainNow: 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.
A monorepo workflow
Section titled “A monorepo workflow”The setup most teams on a large monorepo converge on, start to finish.
-
Clone without checking out, and without file contents:
Terminal window git clone --filter=blob:none --no-checkout <url> monorepocd monorepo -
Select the paths you need, with the sparse index enabled:
Terminal window git sparse-checkout set --sparse-index services/api libs/sharedInclude shared libraries your service depends on — a build that cannot see
libs/sharedwill fail in a way that is not obviously a checkout problem. -
Check out:
Terminal window git checkout mainOnly the selected paths are written, and only their blobs are fetched.
-
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:
git sparse-checkout add libs/authPerformance expectations
Section titled “Performance expectations”What actually gets faster, and by how much, depends on which bottleneck you had.
| Operation | Effect of sparse checkout |
|---|---|
git clone | None — combine with partial clone |
git checkout / switch | Faster; fewer files written |
git status | Faster with --sparse-index; modest without |
git add | Faster with --sparse-index |
git log, git grep | Unchanged — they cover the whole repository |
| Editor indexing | Substantially faster; far fewer files |
| Disk usage | Working 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 and worktrees
Section titled “Sparse checkout and worktrees”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.
git worktree add ../api maincd ../api && git sparse-checkout set services/api
git worktree add ../web -b web-work maincd ../web && git sparse-checkout set services/webEach directory is small and focused; the history is stored once.
Checking which paths a rule matches
Section titled “Checking which paths a rule matches”Two commands answer “why is this file here?” or “why is it not?”.
git sparse-checkout check-rulesReads paths on standard input and prints those the current rules would include:
printf 'services/api/main.py\ndocs/index.md\n' | git sparse-checkout check-rulesThis 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:
git ls-files -v | grep '^S' | headS docs/file.txtS libs/shared/file.txtS services/web/file.txtA 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.
Limitations and gotchas
Section titled “Limitations and gotchas”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:
git log -- services/apiA 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.
Common mistakes
Section titled “Common mistakes”“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.
Working with a sparse checkout day to day
Section titled “Working with a sparse checkout day to day”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:
git sparse-checkout reapplyA file you need is missing. Add its directory:
git sparse-checkout add libs/authYour 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:
git ls-files -v | grep '^S' | headCleaning up leftover excluded files — occasionally a path is excluded while a stale copy remains on disk. Recent Git versions provide a subcommand for this:
git sparse-checkout cleanIt is not available in every version. Check what your Git offers:
git sparse-checkoutusage: 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.
Mental Model
Section titled “Mental Model”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.
What You Learned
Section titled “What You Learned”- 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.
setreplaces the list,addappends,listshows it,disablerestores the full tree.git sparse-checkout initis deprecated.--sparse-indexshrinks 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.
Try It Yourself
Section titled “Try It Yourself”-
Build a small monorepo:
Terminal window mkdir ~/sparse-lab && cd ~/sparse-lab && git initfor d in services/api services/web libs/shared docs; domkdir -p $d && echo "content of $d" > $d/file.txtdoneecho "root readme" > README.mdgit add . && git commit -m "Initial monorepo" -
Confirm everything is present:
find . -path ./.git -prune -o -type f -print | sort. -
Narrow it:
Terminal window git sparse-checkout set services/apifind . -path ./.git -prune -o -type f -print | sortPredict first: will
README.mdstill be there? -
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 -
Confirm the file is still tracked:
Terminal window git ls-tree -r --name-only HEAD -
Add a second directory:
git sparse-checkout add libs/shared, then check the tree again. -
Inspect the config:
git sparse-checkout listandgit config --get core.sparseCheckoutCone. -
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.
Next Lesson
Section titled “Next Lesson”Sparse checkout controls what lands on disk. Partial clone controls what arrives over the network.