feat(desktop): per-project sparse checkout for new worktrees

Merged
IH
3mo ago
+2022-65

What

Closes #6035

Adds a per-project Sparse checkout option. A project can list the folders its worktrees actually need; every worktree Superset creates for that project afterwards contains only those folders plus the repo-root files. Empty means a full checkout, exactly as today.

For a large monorepo this is the difference between materialising the whole tree per workspace and materialising the couple of packages you're working in.

How

Uses the recipe from git-worktree(1) so excluded folders are never written to disk, rather than written and then pruned:

git worktree add --no-checkout <path> ...
git -C <path> sparse-checkout set --cone -- apps/desktop packages/ui
git -C <path> checkout

Cone mode always keeps the files at the repo root, so root manifests and configs are present regardless of what's listed.

  • Schema — sparse_checkout_paths on the host-service projects table (migration 0013, generated with drizzle-kit generate). A JSON array, but that encoding never leaves sparse-checkout.ts; the tRPC layer takes and returns string[].
  • Behaviour — addWorktreeWithSparseCheckout wraps all four git worktree add call sites in workspaces.create: new branch, existing local branch, remote-tracking branch, and both PR paths.
  • API — project.setSparseCheckoutPaths (normalises, de-dupes, rejects .. traversal, caps at 200 entries); project.get returns the parsed list.
  • UI — new SparseCheckoutSection in v2 project settings, autosaving on a 500 ms debounce with a Saving…/Saved indicator, matching the Scripts editor directly below it. Registered in settings search.

Scope and edge cases

  • Only worktrees Superset creates. Adopted worktrees keep whatever checkout they already have.
  • Read at creation time, so changing the setting never rewrites existing worktrees.
  • The main repo is never sparsened.
  • A rejected cone falls back to a full checkout rather than failing workspace creation — a working worktree beats a failed one.
  • A failure after the add removes the worktree, so callers keep their "a throw means nothing was created" assumption and their existing rollback paths stay correct.
  • Older hosts that don't return the field degrade to a full checkout.

Testing

packages/host-service/test/integration/sparse-checkout.integration.test.ts builds a real git repo, runs the real workspaces.create, and asserts the worktree on disk: package.json and apps/desktop/file.txt present, apps/web and packages absent.

sparse-checkout.test.ts covers path normalisation, traversal rejection, the JSON round-trip, and the git command sequence including both failure paths (cone rejected → full checkout; checkout fails → worktree removed).

  • 20 new tests; full host-service suite 924 pass / 0 fail
  • Desktop tsc 0 errors, bun run lint exit 0
  • Verified end-to-end in the running app over CDP: typing into the field alone (no save button) took the host row from NULL to ["apps/desktop","packages/host-service"]

Known gaps

  • A typo'd folder is silent. sparse-checkout set --cone -- apps/nope exits 0 and yields a root-files-only worktree. Validating entries against the repo tree needs a directory-listing query that was deliberately scoped out; worth a follow-up.
  • The autosave state machine has no automated test. The desktop app has SSR-only renderer tests — no testing-library or happy-dom — so debounce/coalescing were verified over CDP rather than in CI.
  • If you exclude packages an included app depends on, setup scripts like bun install will fail in those worktrees. That's inherent to sparse checkout.

Open in Stage

Summary by cubic

Adds per-project sparse checkout for new worktrees so only listed folders plus repo-root files are materialized. Also adds a resilient, debounced settings editor that retries transient saves, surfaces invalid input, and avoids stale updates across project/host switches.

  • New Features

    • Project settings: new “Sparse checkout” editor (v2) with 500 ms autosave; queues edits while saving; retries with exponential backoff up to 30s; stops on BAD_REQUEST and shows the reason; clears timers on unmount; preserves drafts; isolates state per project and host; prevents stale retries/timers after switches.
    • API (packages/host-service): project.setSparseCheckoutPaths normalizes, de-dupes, rejects .. and segments starting with -, bounds input at the schema edge, caps at 200 post-normalization; project.get re-normalizes and drops unreadable entries.
    • Worktree creation: git worktree add --no-checkout → sparse-checkout set --cone <paths> → checkout; tolerates post-checkout hook failures; falls back to full checkout if patterns are rejected; removes the worktree on checkout failure; repo-root files always included; adopted worktrees unaffected; setting is read at creation time only.
    • Tests: unit + integration cover normalization, read-side hardening, on-disk results, editor autosave/retry/invalid behavior, and save-race scenarios.
  • Migration

    • Apply host-service migration 0014 to add projects.sparse_checkout_paths.
    • No changes to existing worktrees; hosts that don’t return the field default to a full checkout.

Summary by CodeRabbit

  • New Features
    • Added Project Sparse Checkout to v2 project settings with a folder paths editor.
    • Newly created worktrees apply configured sparse folders while retaining required root files.
    • Settings are searchable, persisted, and can be cleared to restore full checkout.
  • Bug Fixes
    • Added safe fallback and cleanup when sparse patterns cannot be applied.
  • Tests
    • Added coverage for validation, persistence, normalization, and workspace behavior.