Git Worktree: Work on Multiple Branches Simultaneously
TL;DR
Git worktree creates multiple working directories from one repository. Check out different branches side-by-side without stashing. Faster than multiple clones, cleaner than branch switching.
I used to work like this: coding on a feature branch, get pinged about a production bug, stash my changes, switch to main, fix the bug, switch back, pop the stash, and try to remember what I was doing.
Every context switch wasted 5-10 minutes. Stashing was unreliable. I'd forget what was stashed. Sometimes the stash wouldn't apply cleanly.
Then I discovered git worktree and everything changed. Now I have multiple branches checked out simultaneously in different directories. No more stashing, no more context switching, no more losing my place.
Here's how it works and why you should be using it.
What Git Worktree Does
Git worktree lets you check out multiple branches from the same repository into different directories:
project/
├── main/ ← main branch
│ ├── src/
│ └── package.json
├── feature-auth/ ← feature branch
│ ├── src/
│ └── package.json
└── hotfix-security/ ← hotfix branch
├── src/
└── package.json
All three directories share the same .git repository. You can work on all three simultaneously without switching branches.
The Old Way vs Git Worktree
Without Git Worktree
# Working on feature branch
git checkout feature-auth
# ... editing code ...
# Production bug reported!
git stash
git checkout main
git pull
# ... fix bug ...
git add .
git commit -m "Fix critical bug"
git push
# Back to feature work
git checkout feature-auth
git stash pop
# Conflicts? Figure out what you had changed...
Every switch takes time. You lose context. Stashes can conflict.
With Git Worktree
# One-time setup
cd ~/projects/myapp
git worktree add ../myapp-main main
git worktree add ../myapp-feature feature-auth
# Now work simultaneously
cd ~/projects/myapp-feature
# ... edit feature code in VS Code ...
# Production bug? Just open a different directory
cd ~/projects/myapp-main
# ... fix bug in another editor window ...
git commit -m "Fix critical bug"
git push
# Continue feature work immediately
cd ~/projects/myapp-feature
# All your changes still there, no stashing
Both directories are open simultaneously. No context switching.
Basic Usage
Create a New Worktree
# In your main repository
cd ~/projects/myapp
# Create worktree for existing branch
git worktree add ../myapp-feature feature-branch
# Create worktree for new branch
git worktree add -b new-feature ../myapp-new-feature
# Create worktree from remote branch
git worktree add ../myapp-bugfix origin/bugfix-123
This creates a new directory ../myapp-feature with feature-branch checked out.
List All Worktrees
git worktree list
# Output:
# /home/user/projects/myapp abc1234 [main]
# /home/user/projects/myapp-feature def5678 [feature-branch]
# /home/user/projects/myapp-bugfix ghi9012 [bugfix-123]
Shows all worktrees and their current branches.
Remove a Worktree
# Delete the directory
rm -rf ../myapp-feature
# Clean up git tracking
git worktree prune
# Or do both at once
git worktree remove ../myapp-feature
Real-World Workflows
Workflow 1: Feature Development + Code Review
# Main working directory
cd ~/projects/app
git worktree add ../app-main main
git worktree add ../app-feature feature-oauth
# Develop in feature branch
cd ~/projects/app-feature
# ... write code ...
# PR comes in for review
cd ~/projects/app
git worktree add ../app-review pr-1234
cd ../app-review
# Review PR in separate directory
# Run tests, check code
# Meanwhile, feature work is untouched in ../app-feature
# Done reviewing
cd ~/projects/app
git worktree remove ../app-review
Review PRs without disrupting your work.
Workflow 2: Compare Branches Side-by-Side
# Compare implementation approaches
git worktree add ../app-approach-a feature-a
git worktree add ../app-approach-b feature-b
# Open both in different editor windows
code ~/projects/app-approach-a
code ~/projects/app-approach-b
# Compare files directly
diff app-approach-a/src/auth.js app-approach-b/src/auth.js
# Or use your diff tool
meld app-approach-a app-approach-b
See different implementations side-by-side without switching.
Workflow 3: Hotfix While Developing
# Working on feature
cd ~/projects/app-feature
# ... coding ...
# Critical bug in production!
cd ~/projects/app
git worktree add ../app-hotfix main
cd ../app-hotfix
# Fix bug
git commit -m "Fix critical auth bug"
git push origin main
# Deploy the hotfix
./deploy.sh
# Back to feature immediately
cd ~/projects/app-feature
# All your work exactly as you left it
No stashing, no losing your place.
Workflow 4: Long-Running Feature Branches
# Feature takes 2 weeks, need to work on other stuff
git worktree add ../app-feature-big big-refactor
# Small bug fixes on main
git worktree add ../app-main main
# Switch between them freely
cd ~/projects/app-main # Quick fixes
cd ~/projects/app-feature-big # Big feature work
# Both environments stay configured
# Dependencies installed separately
# No reinstalling node_modules when switching
Worktree vs Multiple Clones
You might think: "I'll just clone the repo multiple times." Here's why worktree is better:
Multiple Clones
git clone git@github.com:user/repo.git repo-main
git clone git@github.com:user/repo.git repo-feature
git clone git@github.com:user/repo.git repo-hotfix
# Problems:
# - 3x disk space (entire .git history copied 3 times)
# - 3x network traffic for pulls
# - Branches not synced between clones
# - Fetch/pull needed in each directory
Git Worktree
cd repo
git worktree add ../repo-main main
git worktree add ../repo-feature feature
git worktree add ../repo-hotfix hotfix
# Benefits:
# - Shared .git directory (1x disk space)
# - One fetch updates all worktrees
# - Branches synced automatically
# - Commits visible immediately in all worktrees
Disk usage comparison:
# Multiple clones of 500MB repo
repo-main/.git: 500MB
repo-feature/.git: 500MB
repo-hotfix/.git: 500MB
Total: 1.5GB
# Worktrees of same repo
repo/.git: 500MB
repo-main: 5MB (working files only)
repo-feature: 5MB
repo-hotfix: 5MB
Total: 515MB (3x smaller)
Advanced Patterns
Lock Worktrees to Prevent Accidents
# Lock a worktree (prevents deletion)
git worktree lock ../app-production
# Try to remove (fails)
git worktree remove ../app-production
# Error: working tree locked
# Unlock when needed
git worktree unlock ../app-production
Useful for critical branches you don't want accidentally deleted.
Move Worktrees
# Create worktree
git worktree add ../app-feature feature
# Move it to different location
mv ../app-feature ~/different/path/app-feature
# Update git tracking
git worktree move ../app-feature ~/different/path/app-feature
Or just move and git will figure it out on next command.
Worktrees with Bare Repositories
This is my preferred setup:
# Clone as bare repository (no working directory)
git clone --bare git@github.com:user/repo.git repo.git
cd repo.git
# Create worktrees for everything
git worktree add ../repo-main main
git worktree add ../repo-dev develop
git worktree add ../repo-feature feature-auth
# Structure:
# repo.git/ ← bare repo (just git data)
# repo-main/ ← main branch worktree
# repo-dev/ ← develop branch worktree
# repo-feature/ ← feature branch worktree
Benefits:
- Clean separation between git data and working files
- No "main" working directory
- All branches treated equally
- Easy to manage
Worktrees for CI/CD Testing
# Test build on different branches
git worktree add ../app-build-main main
git worktree add ../app-build-feature feature
# Build both
cd ../app-build-main && npm run build
cd ../app-build-feature && npm run build
# Compare bundle sizes
du -sh app-build-main/dist
du -sh app-build-feature/dist
# Cleanup
git worktree remove ../app-build-main
git worktree remove ../app-build-feature
Worktrees for Documentation
# Separate worktree for GitHub Pages
git worktree add ../docs gh-pages
# Edit docs in separate directory
cd ../docs
# ... update documentation ...
git commit -m "Update API docs"
git push
# Main development untouched
cd ~/projects/app
Integration with IDEs
VS Code
# Create worktrees
git worktree add ../app-main main
git worktree add ../app-feature feature
# Open multiple VS Code windows
code ~/projects/app-main
code ~/projects/app-feature
# Or use VS Code workspace
cat > app.code-workspace <<EOF
{
"folders": [
{ "path": "../app-main", "name": "Main" },
{ "path": "../app-feature", "name": "Feature" }
]
}
EOF
code app.code-workspace
JetBrains IDEs (IntelliJ, WebStorm)
# Create worktrees
git worktree add ../app-main main
git worktree add ../app-feature feature
# Open each as separate project
idea ~/projects/app-main
idea ~/projects/app-feature
# Or use multiple windows
# File → Open → Select each worktree
IDEs handle worktrees as regular directories. Just works.
Common Pitfalls and Solutions
Pitfall 1: Checking Out Same Branch Twice
# This fails
git worktree add ../app-feature feature
git worktree add ../app-feature-2 feature
# Error: 'feature' is already checked out at '../app-feature'
Solution: One worktree per branch. Create a new branch if needed:
git worktree add -b feature-copy ../app-feature-2 feature
Pitfall 2: Forgetting to Prune
# Delete worktree directory manually
rm -rf ../app-feature
# Git still thinks it exists
git worktree list
# Shows deleted worktree
# Clean up
git worktree prune
Better: Always use git worktree remove instead of rm -rf.
Pitfall 3: Node Modules / Build Artifacts
# Each worktree needs its own dependencies
cd ../app-main
npm install
cd ../app-feature
npm install # Install again for this worktree
This is actually a feature - each worktree has isolated dependencies.
Tip: Add .worktree/ to .gitignore if you use a consistent naming pattern:
# .gitignore
.worktree-*
../*-worktree/
Pitfall 4: Submodules
Submodules and worktrees can conflict:
# Main repo with submodules
git worktree add ../app-feature feature
cd ../app-feature
git submodule update --init # Initialize submodules for this worktree
Each worktree needs its own submodule initialization.
Performance Benefits
Real numbers from my workflow:
Branch switching (old way):
git stash # 2s
git checkout main # 3s (large repo)
# ... work ...
git checkout feature # 3s
git stash pop # 2s
# Total: ~10s per switch
# 10 switches/day = 100s wasted
With worktrees:
cd ../app-main # 0.1s
# ... work ...
cd ../app-feature # 0.1s
# Total: 0.2s per switch
# 10 switches/day = 2s
Time saved: 98 seconds per day = 7 hours per year.
Plus no mental overhead from stashing/unstashing.
My Worktree Setup
Here's my standard setup for a new project:
#!/bin/bash
# setup-worktrees.sh
# Clone as bare repository
git clone --bare git@github.com:user/repo.git repo.git
cd repo.git
# Create worktrees for standard branches
git worktree add ../repo-main main
git worktree add ../repo-dev develop
# Create worktree for current feature
git worktree add -b feature/new-feature ../repo-feature
# Setup complete
echo "Worktrees created:"
git worktree list
echo ""
echo "Open in VS Code:"
echo " code ../repo-main"
echo " code ../repo-dev"
echo " code ../repo-feature"
I run this script for every new project.
Shell Aliases
Add to .bashrc or .zshrc:
# Create worktree aliases
alias gwa='git worktree add'
alias gwl='git worktree list'
alias gwr='git worktree remove'
alias gwp='git worktree prune'
# Quick worktree for new feature
gwf() {
git worktree add -b "feature/$1" "../$(basename $PWD)-$1"
}
# Usage:
# gwf auth-system
# Creates: ../myapp-auth-system with branch feature/auth-system
# Quick worktree for PR review
gwpr() {
git fetch origin pull/$1/head:pr-$1
git worktree add "../$(basename $PWD)-pr-$1" pr-$1
}
# Usage:
# gwpr 123
# Creates: ../myapp-pr-123 with PR #123 code
When NOT to Use Worktrees
Worktrees aren't always the answer:
Don't use for:
- Single quick fix (just switch branches)
- Temporary experiments (use
git stashor new branch) - One-time builds (checkout in temp directory)
Do use for:
- Long-running feature branches
- Frequent context switching
- Comparing branches side-by-side
- Code reviews while developing
- Maintaining multiple versions
Cleanup Script
Automated cleanup of old worktrees:
#!/bin/bash
# cleanup-worktrees.sh
# List worktrees older than 30 days
git worktree list --porcelain | grep -A 3 "worktree" | while read line; do
if [[ $line == worktree* ]]; then
path=${line#worktree }
if [[ -d $path ]]; then
age=$(find "$path" -maxdepth 0 -mtime +30)
if [[ -n $age ]]; then
echo "Removing old worktree: $path"
git worktree remove "$path"
fi
fi
fi
done
# Prune removed worktrees
git worktree prune
Run weekly via cron:
# crontab -e
0 2 * * 0 cd ~/projects/myapp && ~/scripts/cleanup-worktrees.sh
Migrating to Worktrees
If you have existing multiple clones:
# Current setup (multiple clones)
~/projects/app/ # main branch
~/projects/app-feature/ # feature branch
~/projects/app-hotfix/ # hotfix branch
# Migrate to worktrees
cd ~/projects
git clone --bare app/.git app.git
cd app.git
# Recreate as worktrees
git worktree add ../app-main main
git worktree add ../app-feature feature
git worktree add ../app-hotfix hotfix
# Copy any uncommitted work
cp -r ~/projects/app/* ~/projects/app-main/
cp -r ~/projects/app-feature/* ~/projects/app-feature/
# Remove old clones
rm -rf ~/projects/app
rm -rf ~/projects/app-feature
rm -rf ~/projects/app-hotfix
# Done - now using worktrees
The Bottom Line
Git worktree solves the "multiple branches at once" problem elegantly:
Benefits:
- No more stashing/unstashing
- Work on multiple branches simultaneously
- Faster than branch switching
- More efficient than multiple clones
- Keep separate dependency installations
- Compare implementations side-by-side
When to use:
- Long-running feature branches
- Frequent context switches
- Code reviews during development
- Comparing different approaches
- Maintaining multiple releases
Basic commands:
git worktree add <path> <branch> # Create worktree
git worktree list # List worktrees
git worktree remove <path> # Remove worktree
git worktree prune # Clean up deleted worktrees
I've been using worktrees for two years and they've become essential to my workflow. No more losing context when switching tasks. No more stash conflicts. Just multiple directories with different branches, all sharing one repository.
Try it for a week. You won't go back to constant branch switching.