Skip to content

feat: inherit sparse-checkout in new worktrees#186

Open
adamwett wants to merge 2 commits into
coderabbitai:mainfrom
adamwett:feat/sparse-checkout-inheritance
Open

feat: inherit sparse-checkout in new worktrees#186
adamwett wants to merge 2 commits into
coderabbitai:mainfrom
adamwett:feat/sparse-checkout-inheritance

Conversation

@adamwett

@adamwett adamwett commented Jun 29, 2026

Copy link
Copy Markdown

Pull Request

full disclaimer: this is entirely ai generated. works for me & my monorepo. thought i'd share in case the maintainers see a valid usecase for this. let me know if you'd like me to provide my claude code convo.

Description

When creating a new worktree from one that has sparse-checkout enabled, the new worktree now inherits the cone pattern automatically. Controlled by the gtr.sparse.inherit config (default on) and --sparse / --no-sparse flags.

Mechanism: when the base worktree is sparse, git gtr new creates the worktree with --no-checkout (so the full tree is never materialized), then replicates the source's sparse config and patterns — cone mode (sparse-checkout init --cone + set <dirs>) or raw patterns (set --stdin) — and checks out the narrowed working tree.

Adds reusable helpers for sparse-checkout replication:

  • _worktree_path_for_ref — find the worktree that has a given ref checked out
  • _resolve_sparse_source — resolve which worktree's sparse config to inherit (the --from base worktree, falling back to the current worktree), only when it is sparse
  • apply_inherited_sparse — replicate a source worktree's cone (or pattern set) into a new worktree
  • create_worktree gains a no_checkout parameter

Motivation

In large monorepos, a worktree often uses sparse-checkout to materialize only a slice of the tree (e.g. one app + shared packages). Today git gtr new runs a plain git worktree add, which always produces a full checkout regardless of the base worktree's sparse state — so branching off a sparse worktree explodes into the entire repo. This change carries the sparse slice into worktrees you branch off it, keeping feature worktrees lean and fast to create.

Fixes # (issue)

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Other (please describe):

Testing

Added tests/sparse.bats (BATS) covering the new helpers, the --sparse / --no-sparse / config decision precedence, and end-to-end cmd_create inheritance (config replicated, pattern list matches source, working tree narrowed to the cone).

Verified on two platforms:

  • macOS (bash 3.2-compatible / BSD userland): full suite green except one pre-existing cmd_clean test that also fails unchanged on main.
  • Linux — Alpine (musl, bash 5.3, git 2.52, busybox userland) via Docker: all 21 sparse/create tests for this PR pass. The only suite failures are 7 pre-existing trust-marker tests that rely on shasum, which is absent on Alpine (musl ships sha256sum); these fail identically on main and are unrelated to this PR.

Manual Testing Checklist

Tested on:

  • macOS
  • Linux (specify distro: Alpine (Docker, musl))
  • Windows (Git Bash)

Core functionality tested:

  • git gtr new <branch> - Create worktree (full checkout, --from, --sparse, --no-sparse)
  • git gtr go <branch> - Navigate to worktree (incl. go 1 for main repo)
  • git gtr editor <branch> - Open in editor (verified dispatch via --editor)
  • git gtr ai <branch> - Start AI tool (verified dispatch via --ai)
  • git gtr rm <branch> - Remove worktree
  • git gtr list - List worktrees
  • git gtr config - Configuration commands (set/get/list, incl. gtr.sparse.inherit)
  • Other commands affected by this change: git gtr new --sparse / --no-sparse (also smoke-tested run, mv, doctor, adapter, completion, init)

Verified using the installed git gtr subcommand (via ./install.sh), not just ./bin/gtr.

Test Steps

Against a monorepo whose base worktree uses cone-mode sparse-checkout (only apps/<one-app> + packages materialized):

  1. git gtr new feature-xyz --from <sparse-branch>
  2. ls "$(git gtr go feature-xyz)" — only the inherited cone is present; excluded top-level dirs are absent.
  3. git -C "$(git gtr go feature-xyz)" sparse-checkout list — matches the source worktree.
  4. git gtr new other --from <sparse-branch> --no-sparse — full checkout (opt-out works).

Expected behavior:

The worktree created in step 1 has core.sparseCheckout=true (and sparseCheckoutCone=true for cone repos), the same pattern list as the source, and a working tree narrowed to that cone — without ever materializing the full tree. Full-checkout repos are unaffected and still get a full checkout.

Actual behavior:

As expected. Verified live: the new worktree reported Inherited sparse-checkout from <source>, core.sparseCheckout=true, sparseCheckoutCone=true, an identical sparse-checkout list, and the excluded directories were absent from disk.

Breaking Changes

  • This PR introduces breaking changes
  • I have discussed this in an issue first
  • Migration guide is included in documentation

Not a breaking change for full-checkout repositories — they are untouched. For repositories whose worktrees already use sparse-checkout, new worktrees branched from a sparse worktree now inherit the cone instead of getting a full checkout. This is opt-out at any time via --no-sparse or globally via git gtr config set gtr.sparse.inherit false.

Checklist

Before submitting this PR, please check:

  • I have read CONTRIBUTING.md
  • My code follows the project's style guidelines
  • I have performed manual testing on at least one platform
  • I have updated documentation (README.md, CLAUDE.md, etc.) if needed
  • My changes work on multiple platforms (or I've noted platform-specific behavior)
  • I have added/updated shell completions (if adding new commands or flags)
  • I have tested with both git gtr (production) and ./bin/gtr (development)
  • No new external dependencies are introduced (Bash + git only)
  • All existing functionality still works

Additional Context

Scope of changes (13 files): sparse logic and helpers in lib/core.sh; flags, decision, and post-create apply in lib/commands/create.sh; the gtr.sparse.inheritsparse.inherit key mapping in lib/config.sh; help text; the --sparse/--no-sparse flags in the completion generator scripts/generate-completions.sh plus the regenerated completions/{gtr.bash,_git-gtr,git-gtr.fish}; README, docs/configuration.md, docs/advanced-usage.md, and templates/.gtrconfig.example; plus tests/sparse.bats.

Implementation notes:

  • Pure Bash + git, Bash 3.2 compatible (no mapfile); all git config reads are guarded against set -e.
  • git sparse-checkout set after --no-checkout does not populate the working tree on its own, so apply_inherited_sparse runs an explicit git checkout to materialize the cone.
  • Both cone and non-cone sparse modes are handled; if any sparse step fails the worktree is still created (it just falls back to a full checkout) — fail-safe per the project's design principles.
  • Shell completions are produced by scripts/generate-completions.sh; the new flags were added to the generator and the files regenerated, so ./scripts/generate-completions.sh --check (the CI completions check) passes. Regenerating also picked up gtr.sparse.inherit in the config-key completions automatically.
  • The full BATS suite passes locally with the exception of one cmd_clean --merged uses nested registered worktree path test that also fails unchanged on main in this environment (pre-existing, unrelated to this PR).

License Acknowledgment

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache License 2.0.

Summary by CodeRabbit

  • New Features

    • New worktrees can now inherit sparse-checkout settings from a base worktree by default.
    • Added command-line options to explicitly enable or disable sparse inheritance when creating a worktree.
  • Documentation

    • Updated configuration and usage docs with sparse-checkout inheritance guidance, overrides, and examples.
    • Added the new setting to the sample configuration file.
  • Bug Fixes

    • Improved shell completions and help text so the new sparse-related options and settings appear in command suggestions.

When creating a new worktree from one with sparse-checkout enabled,
the new worktree inherits the cone pattern automatically. Controlled
by gtr.sparse.inherit config (default on) and --sparse/--no-sparse
flags. Adds reusable helpers for sparse-checkout replication.
@adamwett adamwett requested a review from NatoBoram as a code owner June 29, 2026 00:41
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0db840fa-082d-46fc-a7d6-58be3b2dd80e

📥 Commits

Reviewing files that changed from the base of the PR and between f85e0f4 and c0b439f.

📒 Files selected for processing (3)
  • lib/commands/create.sh
  • lib/core.sh
  • tests/sparse.bats
 ___________________________________________________________________
< Your tests are like unicorns: frequently referenced, rarely seen. >
 -------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

Walkthrough

Adds sparse-checkout inheritance to git gtr new via two new CLI flags (--sparse, --no-sparse) and a gtr.sparse.inherit config key (default true). Core helpers resolve sparse sources and replicate cone/non-cone patterns into newly created worktrees. Shell completions, docs, config template, and a new Bats test suite are included.

Changes

Sparse-Checkout Inheritance

Layer / File(s) Summary
Core sparse helpers and create_worktree update
lib/core.sh, lib/config.sh
Adds _worktree_path_for_ref, _resolve_sparse_source, and apply_inherited_sparse helpers; extends create_worktree with an optional no_checkout parameter; registers gtr.sparse.inheritsparse.inherit in the config key map.
cmd_create flag parsing and decision block
lib/commands/create.sh, lib/commands/help.sh
Parses --sparse/--no-sparse into sparse_flag/no_sparse_flag; computes sparse_inherit with precedence --no-sparse > --sparse > config; calls apply_inherited_sparse after worktree creation.
Bats test suite
tests/sparse.bats
Tests all three new helpers and cmd_create sparse/no-sparse/non-sparse scenarios using cone-mode worktrees.
Shell completions
completions/gtr.bash, completions/_git-gtr, completions/git-gtr.fish, scripts/generate-completions.sh
Adds --sparse/--no-sparse to new subcommand completions and gtr.sparse.inherit to config key completions across bash, zsh, and fish.
Docs and config template
README.md, docs/advanced-usage.md, docs/configuration.md, templates/.gtrconfig.example
Documents sparse-checkout inheritance behavior, gtr.sparse.inherit default, per-command overrides, and adds a [sparse] section to the example config.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hopping through the worktree maze,
sparse paths copied, full-tree haze!
--no-sparse if you dare,
gtr.sparse.inherit set with care.
Only your cone dirs appear—
the rabbit keeps monorepos clear! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: sparse-checkout inheritance for newly created worktrees.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
tests/sparse.bats (2)

116-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise sparse config/flag precedence explicitly.

The command tests cover the default-on path and --no-sparse, but they never assert gtr.sparse.inherit=false or --sparse overriding that config. That leaves the precedence block in lib/commands/create.sh only partially covered, even though the feature contract depends on it.

Suggested test additions
+@test "cmd_create honors gtr.sparse.inherit=false" {
+  source_gtr_commands
+  make_sparse_worktree "$TEST_WORKTREES_DIR/base" base apps/web
+  git -C "$TEST_REPO" config gtr.sparse.inherit false
+
+  run cmd_create feat-config-off --from base --yes --no-fetch --no-hooks --no-copy
+  [ "$status" -eq 0 ]
+
+  wt="$TEST_WORKTREES_DIR/feat-config-off"
+  [ -d "$wt/apps/api" ]
+  [ "$(git -C "$wt" config --bool core.sparseCheckout 2>/dev/null || echo false)" != "true" ]
+}
+
+@test "cmd_create --sparse overrides gtr.sparse.inherit=false" {
+  source_gtr_commands
+  make_sparse_worktree "$TEST_WORKTREES_DIR/base" base apps/web
+  git -C "$TEST_REPO" config gtr.sparse.inherit false
+
+  run cmd_create feat-config-override --from base --sparse --yes --no-fetch --no-hooks --no-copy
+  [ "$status" -eq 0 ]
+
+  wt="$TEST_WORKTREES_DIR/feat-config-override"
+  [ "$(git -C "$wt" config --bool core.sparseCheckout)" = "true" ]
+  [ -d "$wt/apps/web" ]
+  [ ! -d "$wt/apps/api" ]
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sparse.bats` around lines 116 - 154, Add tests in sparse.bats to cover
sparse precedence in cmd_create: explicitly verify that gtr.sparse.inherit=false
prevents sparse-checkout inheritance from a sparse --from worktree, and that
--sparse overrides that config to keep sparse checkout enabled. Reuse the
existing cmd_create, make_sparse_worktree, and git -C "$wt" config assertions so
the new cases exercise the precedence logic in lib/commands/create.sh alongside
the current default-on and --no-sparse coverage.

93-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a non-cone inheritance test.

This only exercises the core.sparseCheckoutCone=true branch. apply_inherited_sparse() has a separate non-cone path that initializes sparse-checkout without --cone and pipes raw patterns through set --stdin, so a regression there would currently ship untested.

Suggested test shape
+@test "apply_inherited_sparse replicates non-cone patterns into a new worktree" {
+  git -C "$TEST_REPO" worktree add --quiet -b base-noncone "$TEST_WORKTREES_DIR/base-noncone" HEAD
+  git -C "$TEST_WORKTREES_DIR/base-noncone" sparse-checkout init --no-cone >/dev/null
+  printf 'apps/web/*\n!apps/web/file.txt\npackages/*\n' \
+    | git -C "$TEST_WORKTREES_DIR/base-noncone" sparse-checkout set --stdin >/dev/null
+  git -C "$TEST_REPO" worktree add --no-checkout --quiet -b feat-noncone "$TEST_WORKTREES_DIR/feat-noncone" base-noncone
+
+  run apply_inherited_sparse "$TEST_WORKTREES_DIR/feat-noncone" "$TEST_WORKTREES_DIR/base-noncone"
+  [ "$status" -eq 0 ]
+  [ "$(git -C "$TEST_WORKTREES_DIR/feat-noncone" config --bool core.sparseCheckout)" = "true" ]
+  [ "$(git -C "$TEST_WORKTREES_DIR/feat-noncone" config --bool core.sparseCheckoutCone 2>/dev/null || echo false)" != "true" ]
+  [ "$(git -C "$TEST_WORKTREES_DIR/base-noncone" sparse-checkout list)" = "$(git -C "$TEST_WORKTREES_DIR/feat-noncone" sparse-checkout list)" ]
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sparse.bats` around lines 93 - 114, Add a test that covers the non-cone
inheritance path in apply_inherited_sparse, since the current sparse.bats case
only verifies core.sparseCheckoutCone=true. Create a fixture where the source
worktree uses non-cone sparse-checkout patterns, then assert the inherited
worktree preserves core.sparseCheckout=true, keeps core.sparseCheckoutCone
false, and applies the raw patterns via sparse-checkout set --stdin behavior
rather than cone directories. Use apply_inherited_sparse, make_sparse_worktree,
and sparse-checkout list to locate and validate the non-cone branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/commands/create.sh`:
- Around line 200-206: The sparse-checkout inheritance failure is being
downgraded to a warning in the create_worktree flow, which can incorrectly
continue after create_worktree with no_checkout set and leave a partially
initialized worktree. Update the create.sh logic around apply_inherited_sparse
so that a failure is treated as a hard error (or otherwise triggers a proper
fallback) before the copy/hooks/success path continues, and make sure the
behavior is enforced in the create_worktree/no_checkout branch rather than only
logging through log_warn.

In `@lib/core.sh`:
- Around line 622-630: The worktree selection in the sparse-checkout helper
currently stops at the first matching ref even when that worktree is not
sparse-enabled, causing the fallback path to be skipped. Update the logic in the
helper that uses _worktree_path_for_ref and the core.sparseCheckout check so it
only accepts a matching worktree when sparse checkout is true; otherwise
continue falling back to the current repo/top-level worktree instead of
returning empty. Keep the existing behavior for enabled sparse worktrees, but
ensure non-sparse matches do not short-circuit the search.
- Around line 593-603: The branch matching in the worktree lookup is stripping
too much from remote refs, causing slash-separated names like feature/user-auth
to lose their prefix and fail to match. Update the normalization logic in the
worktree scan around the ref_short/branch comparison so it preserves the branch
path after the remote name instead of using only the last path segment, and keep
the comparison in the same block that reads worktree and branch entries.
- Around line 640-676: The apply_inherited_sparse function currently assumes git
sparse-checkout init/list/set --stdin are available, but that only works on Git
2.25+, so older supported clients can leave the new worktree unmaterialized. Add
a Git version guard in apply_inherited_sparse and, when the src_wt/new_wt
commands are unsupported, either skip sparse inheritance entirely or fall back
to a normal checkout so the new worktree is fully populated. Keep the existing
log_warn paths for real failures, and use the apply_inherited_sparse flow to
locate the compatibility branch.

---

Nitpick comments:
In `@tests/sparse.bats`:
- Around line 116-154: Add tests in sparse.bats to cover sparse precedence in
cmd_create: explicitly verify that gtr.sparse.inherit=false prevents
sparse-checkout inheritance from a sparse --from worktree, and that --sparse
overrides that config to keep sparse checkout enabled. Reuse the existing
cmd_create, make_sparse_worktree, and git -C "$wt" config assertions so the new
cases exercise the precedence logic in lib/commands/create.sh alongside the
current default-on and --no-sparse coverage.
- Around line 93-114: Add a test that covers the non-cone inheritance path in
apply_inherited_sparse, since the current sparse.bats case only verifies
core.sparseCheckoutCone=true. Create a fixture where the source worktree uses
non-cone sparse-checkout patterns, then assert the inherited worktree preserves
core.sparseCheckout=true, keeps core.sparseCheckoutCone false, and applies the
raw patterns via sparse-checkout set --stdin behavior rather than cone
directories. Use apply_inherited_sparse, make_sparse_worktree, and
sparse-checkout list to locate and validate the non-cone branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2aeb9dfc-e4e0-4bd2-8819-9bf9289313c3

📥 Commits

Reviewing files that changed from the base of the PR and between ad7a3c5 and f85e0f4.

📒 Files selected for processing (13)
  • README.md
  • completions/_git-gtr
  • completions/git-gtr.fish
  • completions/gtr.bash
  • docs/advanced-usage.md
  • docs/configuration.md
  • lib/commands/create.sh
  • lib/commands/help.sh
  • lib/config.sh
  • lib/core.sh
  • scripts/generate-completions.sh
  • templates/.gtrconfig.example
  • tests/sparse.bats

Comment thread lib/commands/create.sh Outdated
Comment thread lib/core.sh Outdated
Comment thread lib/core.sh Outdated
Comment thread lib/core.sh
…heritance

- Preserve slash-separated branch paths in _worktree_path_for_ref
- Allow fallback to top-level worktree when matching worktree is not sparse
- Add Git 2.25+ guard for sparse-checkout support with full checkout fallback
- Fix non-cone mode to use init --no-cone (git defaults to cone mode)
- Improve error handling: failed sparse inheritance now falls back to full
  checkout and hard-errors if that fails, instead of leaving --no-checkout
  worktree empty
- Add tests for slash refs, non-cone inheritance, and sparse config precedence
@adamwett

adamwett commented Jun 29, 2026

Copy link
Copy Markdown
Author

from my agent:

Here's what I fixed against each finding:

core.sh

  • _worktree_path_for_ref (remote ref matching): changed ${ref##/} (last segment only) → ${ref#/} (strip only the remote name), so origin/feature/user-auth now matches the branch feature/user-auth instead of collapsing to user-auth.
  • _resolve_sparse_source (no short-circuit on non-sparse match): a matching-but-non-sparse worktree no longer returns empty — it now falls through to checking the current/top-level worktree. Extracted a _worktree_is_sparse helper.
  • apply_inherited_sparse (Git version guard): added _git_supports_sparse_checkout (Git 2.25+ check). On older clients it falls back to a full git checkout so the worktree is populated rather than left empty.
  • Bonus real bug: the non-cone branch used sparse-checkout init, which defaults to cone mode on modern git — silently breaking non-cone inheritance. Fixed to init --no-cone. (Surfaced by the new non-cone test.)

create.sh

  • Sparse failure now triggers a proper fallback: since the worktree is created with --no-checkout, a failed apply_inherited_sparse previously left it empty. It now disables sparse and does a full checkout, hard-erroring (exit 1) only if even that fails — before copy/hooks/success proceed.

tests/sparse.bats

  • Added: slash-separated remote ref match, non-cone inheritance, gtr.sparse.inherit=false prevents inheritance, and --sparse overrides that config.

All nitpicks were valid and addressed (the non-cone test caught a genuine latent bug, so no skips were warranted).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant