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 stash or 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.