Git Worktrees
This guide covers using Git worktrees to work on multiple unrelated features in parallel without branch switching or stashing.
What Are Worktrees?
A Git worktree is a linked working directory connected to your repository. Each worktree can have a different branch checked out, allowing you to:
- Work on two unrelated features simultaneously
- Keep separate IDE windows for each feature
- Avoid constant
git stash/git checkoutcycles - Run different branches side-by-side for comparison
# Traditional workflow (single directory)
podcaster-plus-app/ # switch branches constantly
└── .git/
# With worktrees (multiple directories)
podcaster-plus-app/ # main branch
└── .git/
worktrees/
├── feat-rbac/ # feature branch 1
└── feat-guest-network/ # feature branch 2Quick Reference
# Create a worktree for an existing branch
git worktree add ../worktrees/feat-name existing-branch-name
# Create a worktree with a new branch (based on main)
git worktree add ../worktrees/feat-name -b feat/new-feature main
# List all worktrees
git worktree list
# Remove a worktree
git worktree remove ../worktrees/feat-name
# Clean up stale references
git worktree pruneWorkflow
1. Create a Worktree
From your main repository:
cd ~/Documents/GitHub/podcaster-plus-app
# For an existing branch
git worktree add ../worktrees/rbac feat/RBAC-and-access-control
# For a new branch (branches from main)
git worktree add ../worktrees/guest-network -b feat/guest-network main2. Set Up the Worktree
Each worktree needs its own dependencies:
cd ../worktrees/rbac
pnpm install
cp ../podcaster-plus-app/.env .env # Copy environment file3. Open in Your Editor
Open the worktree directory in a new editor window:
- Cursor/VS Code: File → Open Folder → select worktree directory
- Terminal: Each terminal session can be in a different worktree
4. Work Normally
All standard Git operations work within each worktree:
# In worktree directory
git status # Shows status for this branch
git add .
git commit -m "feat: add RBAC middleware"
git push origin feat/RBAC-and-access-control5. Create PR and Merge
Create pull requests as normal. After merging:
# From any directory
git worktree remove ../worktrees/rbac
# Delete the branch (if not auto-deleted)
git branch -d feat/RBAC-and-access-control
# Clean up any stale references
git worktree pruneHandling Merge Conflicts
Updating Your Worktree from Main
When main has changes you need:
cd ../worktrees/feat-rbac
# Fetch latest
git fetch origin
# Option 1: Merge main into your branch
git merge origin/main
# Option 2: Rebase onto main (cleaner history)
git rebase origin/mainResolving Conflicts
Conflicts are resolved the same as normal Git workflows:
# After conflict occurs
git status # See conflicted files
# Edit files to resolve conflicts
code path/to/conflicted-file
# Mark as resolved
git add path/to/conflicted-file
# Continue merge or rebase
git merge --continue
# or
git rebase --continueConflict Between Worktrees
If two worktrees modify the same files, conflicts happen when the second branch merges to main. This is normal Git behavior - worktrees don't prevent this, they just let you work in parallel.
Best practice: Coordinate with the team on who's working on what files.
Project-Specific Considerations
Node Modules
Each worktree needs its own node_modules directory. After creating a worktree:
cd ../worktrees/new-feature
pnpm installDisk Space
Each worktree with node_modules uses ~500MB. Clean up worktrees you're not actively using.
Environment Files
Copy .env from your main directory:
cp ../podcaster-plus-app/.env .envOr create a symlink (shared config):
ln -s ../podcaster-plus-app/.env .envSupabase Local Development
All worktrees share the same local Supabase instance if you're running one. This is usually fine, but be aware:
- Migrations: Running migrations in one worktree affects all
- Data: Test data is shared across worktrees
For isolated testing, use the remote Supabase project instead.
TypeScript and IDE
Each worktree is independent - your IDE sees them as separate projects. Benefits:
- Separate TypeScript servers (no interference)
- Independent ESLint/Prettier configs
- Isolated terminal sessions
Best Practices
Naming Convention
Use short, descriptive names for worktree directories:
# Good - short and clear
../worktrees/rbac
../worktrees/guest-network
../worktrees/fix-booking-bug
# Avoid - too long
../worktrees/feat-RBAC-and-access-control-implementationDirectory Structure
Keep worktrees in a dedicated directory:
~/Documents/GitHub/
├── podcaster-plus-app/ # Main repo (usually on main)
└── worktrees/ # All worktrees here
├── rbac/
├── guest-network/
└── hotfix-123/Limit Active Worktrees
Maintain 2-3 active worktrees maximum:
- Reduces disk space usage
- Prevents context overload
- Easier to track what you're working on
Clean Up After PRs
Remove worktrees immediately after PR merge:
# List current worktrees
git worktree list
# Remove merged feature
git worktree remove ../worktrees/completed-feature
# Prune stale references
git worktree pruneCommon Commands
| Task | Command |
|---|---|
| List worktrees | git worktree list |
| Create (existing branch) | git worktree add <path> <branch> |
| Create (new branch) | git worktree add <path> -b <new-branch> <base> |
| Remove worktree | git worktree remove <path> |
| Force remove | git worktree remove --force <path> |
| Clean stale refs | git worktree prune |
| Move worktree | git worktree move <old-path> <new-path> |
Shell Aliases (Optional)
Add to ~/.zshrc for convenience:
# Git worktree shortcuts
alias gwl="git worktree list"
alias gwp="git worktree prune"
# Create worktree with automatic pnpm install
gwa() {
local name="$1"
local branch="${2:-$1}"
local base="${3:-main}"
local worktree_dir="../worktrees/$name"
if [ -z "$name" ]; then
echo "Usage: gwa <name> [branch] [base]"
return 1
fi
# Check if branch exists
if git show-ref --verify --quiet "refs/heads/$branch"; then
git worktree add "$worktree_dir" "$branch"
else
git worktree add -b "$branch" "$worktree_dir" "$base"
fi
# Install dependencies
cd "$worktree_dir" && pnpm install
echo "Worktree ready at: $worktree_dir"
}
# Remove worktree and optionally delete branch
gwr() {
local name="$1"
local worktree_dir="../worktrees/$name"
if [ -z "$name" ]; then
echo "Usage: gwr <name>"
git worktree list
return 1
fi
git worktree remove "$worktree_dir"
git worktree prune
echo "Removed worktree: $name"
}After adding, reload your shell:
source ~/.zshrcTroubleshooting
"fatal: is already checked out"
A branch can only be checked out in one worktree at a time:
# Find where it's checked out
git worktree list
# Either remove that worktree or use a different branchWorktree Shows Wrong Branch
If the worktree state seems wrong:
cd ../worktrees/problematic
git status
git checkout <correct-branch> # If needed"worktree ... is not a valid path"
The worktree directory was deleted outside Git:
git worktree prune # Cleans up references to deleted directoriesIDE Not Recognizing Git
Ensure your IDE is opened at the worktree root, not a subdirectory. The worktree has a .git file (not folder) that points to the main repo.
When NOT to Use Worktrees
- Small fixes: For quick one-file changes, just commit and switch
- Dependent features: If feature B depends on feature A, work sequentially
- Low disk space: Each worktree with node_modules uses significant space
Summary
Git worktrees enable true parallel development. The workflow is:
- Create worktree for your feature branch
- Install dependencies and copy
.env - Work in separate IDE window
- Merge via normal PR process
- Clean up worktree after merge
Keep worktrees organized, clean up promptly, and coordinate with team members on shared files.