worktree-swap-cli
v1.10.1
Published
Run N feature branches in parallel git worktrees while one main checkout runs the dev servers; swap a worktree's files into the main checkout on demand.
Maintainers
Readme
worktree-swap-cli
Run several feature branches in parallel, each in its own git worktree,
while only one heavy dev-server process runs — in your main checkout,
which acts as a "testbase". worktree apply <name> swaps a feature
worktree's files into the main checkout so your already-running dev/watch
servers pick up the change (hot reload), without restarting anything or
touching git branches.
Install
npm install -g worktree-swap-cliInstalls the worktree command (and a trimmed wt alias — same binary,
either name works). Requires git and rsync on PATH (both ship with
macOS and most Linux distros). If the code CLI (VS Code) is on PATH,
worktree create opens each new worktree in VS Code automatically —
skipped silently if it isn't installed.
Setup
Run these from your main checkout (e.g. ~/code/myapp). Sibling
worktrees are created next to it as ../myapp-<name>.
myapp/ <- main checkout, runs npm run dev, stays put
myapp-feat-1/ <- worktree, branch "feat-1"
myapp-feat-2/ <- worktree, branch "feat-2"Commands
worktree create <name> [from-branch] [--carry-changes] [--staged | --unstaged]
create ../<repo>-<name> worktree + branch <name>,
based on [from-branch] (default: repo's default branch);
--carry-changes moves main's current uncommitted changes into it
(--staged/--unstaged narrow which; require --carry-changes)
worktree apply <name> load <name>'s files into the main checkout
worktree copy <from> <to> [--staged | --unstaged]
move uncommitted changes from one worktree to another
worktree status show which worktree is currently loaded
worktree list wraps `git worktree list`
worktree remove <name> remove the worktree and delete its branch
worktree reset unload any applied worktree, restore main to its own HEADHow apply works
- If the main checkout currently holds another worktree's content
(tracked via a
.active-worktreemarker file), that worktree's files are rsynced back into its own directory first — preserving any uncommitted changes there. Nothing is auto-committed. <name>'s worktree files are rsynced into the main checkout, deleting anything not present in<name>(excludes.git,node_modules,.env,.codegraph).- Your dev-watch process in the main checkout detects the file changes and hot-reloads.
- The
.active-worktreemarker is updated to<name>.
Gitignored files on create
git worktree add never copies untracked/gitignored files. worktree
create backfills two things from the main checkout: node_modules is
symlinked (one shared install), and every CLAUDE.md file — at any depth
— plus .env is copied in by name (via a targeted git ls-files
pathspec, not a full gitignored-file scan — that was tried and reverted
for being slow on repos with large ignored build directories). Nothing
else gitignored is carried automatically.
.codegraph
codegraph keeps a per-directory, gitignored index
(.codegraph/). Since git worktree add never copies untracked files, a
fresh worktree starts without one — and since it's gitignored,
apply/reset never touch it either, so each worktree (and the main
checkout) keeps its own independent index, never shared, overwritten, or
deleted by this tool.
If the codegraph CLI is on PATH, worktree create builds that index
automatically with codegraph init. Skipped silently if codegraph isn't
installed. No symlinking is used — each worktree gets its own real index.
Moving uncommitted changes with copy
worktree copy feat-1 feat-2 # move all uncommitted changes
worktree copy feat-1 feat-2 --staged # move only staged changes
worktree copy feat-1 feat-2 --unstaged # move only unstaged changes (incl. untracked)Reads git status in <from>, copies the matching files into <to>, then
reverts those same files in <from> (restored from HEAD, or deleted if
they were new/untracked). It's a move — after running, the changes exist
only in <to>. Nothing is committed on either side.
Unloading a worktree with reset
apply never changes the main checkout's branch — only its working-tree
files. worktree reset unloads whatever's currently applied, in one of two
modes:
worktree reset # soft: carry back, clear marker, keep main's files as-is
worktree reset --force # hard: soft reset, then also wipe main back to its own branch HEAD
worktree reset --no-carry-back # skip the copy-back step
worktree reset --force --no-carry-back # discard current changes for good — not saved anywhereBoth modes carry the main checkout's current files back into the
previously loaded worktree's directory first — nothing is lost — unless
you pass --no-carry-back to skip that copy. Combined with --force,
--no-carry-back permanently deletes the current changes.
- Soft (no flag): after this, the same changes exist in both the worktree directory and the main checkout. Use this when you just want to stop tracking a worktree as "applied" without touching what's currently on disk in the main checkout.
- Hard (
--force): additionally runsgit checkout -- .andgit clean -fd(excludingnode_modules/.env) to restore the main checkout to a clean copy of its own branch. Safe because the carry-back already happened first.
Rules this tool follows
- The main checkout's git branch (
HEAD) never changes duringapply— only its working-tree files are overwritten.git statusafterwards will show the applied worktree's diff against whatever branch the main checkout is on. This is intentional. - Nothing is ever auto-committed. Uncommitted changes only ever move between directories via rsync, left uncommitted for you to review.
- The very first
applyhas no prior marker. If the main checkout has uncommitted changes at that point, the command refuses to run — commit/stash first, or pass--force. - If the target worktree's
package.jsondiffers from what's currently loaded, the tool prints a reminder to runnpm installand restart your dev servers — this is not automatic. worktree removenever touches the main checkout itself..active-worktreeis auto-added to the main checkout's.gitignore.
Configuration
By default the "main checkout" is the git toplevel of your current directory. Override with:
WORKTREE_MAIN_ROOT=/path/to/main/checkout worktree apply feat-1