# Sparse-checkout worktrees
Riftri supports a deliberately narrow, deterministic slice of Git
sparse checkout: **cone mode with an explicit directory list**, requested
per worktree on the explicit interface:
```console
$ riftri worktree add ../app-core -b feature/core main \
--sparse-dir crates/riftri-core --sparse-dir docs
```
The resulting directory is a real linked Git worktree whose working tree
contains every repository-root file, every file directly inside a listed
directory's ancestors, and everything below the listed directories — exactly
the shape `git sparse-checkout set --cone` produces. Git remains the source of
truth: the immutable base is materialized by Git's own sparse-checkout and
`checkout-index` machinery in an isolated administrative directory, and the new
worktree carries real worktree-scoped sparse configuration
(`core.sparseCheckout` plus the cone directory list), real skip-worktree index
bits, and a verified clean `git status` before Riftri reports success.
Enabling that worktree-scoped configuration turns on Git's standard
`extensions.worktreeConfig` setting for the repository, exactly as running
`git sparse-checkout set` in any linked worktree would; the main worktree and
other worktrees keep their ordinary full-checkout behavior.
## Configuration source
The sparse selection comes from repeated `--sparse-dir
` flags on
`riftri worktree add`. Each directory is repository-relative with `/`
separators.
When no `--sparse-dir` is given, the selection is inherited from the worktree
the command runs in, because that is what Git does: `git worktree add` copies
the current worktree's cone into the new worktree, so an add issued from
inside a sparse worktree is sparse with no sparse argument anywhere on the
command line. Riftri reproduces that deliberately and keys the immutable base
by the inherited profile, so the base always matches what was materialized.
An explicit `--sparse-dir` is an override and always wins over inheritance. The list is canonicalized — sorted, deduplicated, trailing slashes
removed, and nested selections collapsed into their listed ancestors — and the
canonical list becomes part of the versioned checkout profile that keys the
immutable base. Two different selections at the same commit therefore always
build or reuse different bases, a sparse and a full request never share a
base, and repeating an equivalent selection reuses its cached base. Full
(non-sparse) requests are unaffected and keep their existing base identities.
## Refusal behavior
Anything outside this subset fails closed with a precise diagnostic before
Riftri creates lifecycle state, a branch, or Git worktree metadata — a sparse
request is never silently materialized as a full tree, and an unsupported one
never partially materializes:
- Sparse patterns are not accepted: wildcards (`*`, `?`, `[`, `]`), negations
(`!dir`), backslashes, control characters, absolute paths, and `.`/`..`
components are all refused. Only literal directory lists are supported.
- Each listed directory must exist as a directory in the exact requested tree,
so a misspelled selection cannot silently produce a nearly empty worktree.
- Sparse checkout that is enabled but not in cone mode
(`core.sparseCheckout=true` with `core.sparseCheckoutCone` unset or false)
remains an unsupported checkout profile for any optimized add, sparse or
full. Riftri replays the repository's checkout configuration into the
materialization and does not model non-cone pattern semantics, so it refuses
rather than guess — including when an explicit `--sparse-dir` is given.
- Intercepted `git worktree add` commands (process-scoped or shell-hook
activation) cannot *request* a sparse view yet; sparse-looking options are
refused with a pointer to the explicit interface, and `RIFTRI_BYPASS=1`
remains the ordinary-Git escape hatch. An intercepted add from inside a
cone-mode sparse worktree does work, inheriting that worktree's cone exactly
as Git would.
- Trees with Git LFS-managed paths cannot be combined with a sparse selection
yet.
- Compacting a sparse worktree is refused; remove and recreate the worktree to
reset its storage.
## Lifecycle
Sparse views use the same journaled add transaction, private-write isolation,
clean-removal lifecycle, and crash recovery as full views. An unchanged
interrupted sparse add rolls back and can be retried. If a later selection
change materializes a different set of files, Riftri preserves that view for
inspection instead of resetting its index or deleting it.
Git still owns later selection changes. Inside an active view you can run
`git sparse-checkout set --cone another/directory` or `git sparse-checkout
disable`. Those commands update only that view: the original immutable base
and its peers keep their original selection. Newly included files are ordinary
Git checkouts, not additional Riftri clones. Clean removal works after expanding
or disabling sparse checkout, and dirty changes still prevent clean removal.
For a new COW-backed selection, remove the clean view and create a new one with
the desired `--sparse-dir` arguments.
Non-cone creation, file-level selection, explicit sparse options through Git
interception, sparse compaction, and a Riftri-managed profile-change operation
remain future work tracked in the roadmap.