Git Worktrees but Why?
Git Worktrees: How We Replaced Our Entire Branch Management Strategy With a Feature Nobody Uses
We had 14 developers, 3 active releases, and a monorepo that took 12 minutes to clone. Every context switch was killing us. That's when someone suggested using Git's most ignored feature as our entire development workflow.
Here's how we've been running our 400GB monorepo with instant context switching for 18 months—using a Git feature so obscure that most developers don't know it exists.
The Impossible Situation
Picture this: Critical production bug comes in. You're 800 lines deep into a refactoring. The junior dev who could handle it is knee-deep in their own feature branch. The senior who knows that code is reviewing a PR.
What Everyone Said We Needed
# Option 1: The Stash Dance
git stash save "WIP: refactoring auth system"
git checkout hotfix/critical-bug
# ... fix bug ...
git checkout feature/auth-refactor
git stash pop
# Merge conflicts. Always merge conflicts.
# Option 2: Multiple Clones
cd ~/projects/megacorp-app-clone-1
cd ~/projects/megacorp-app-clone-2
cd ~/projects/megacorp-app-clone-3
# 36 minutes and 1.2TB later...
# Option 3: Fancy Branch Management Tool
Price: $49/developer/month
Setup time: 2 weeks
Learning curve: "It's intuitive!" (narrator: it wasn't)
What We Had
Repository size: 400GB
Clone time: 12 minutes (on a good day)
Developers constantly switching contexts: 14
Budget for tools: $0
Patience for complicated workflows: Also $0
The Stupid Idea That Wasn't
Thursday, 3:47 PM. We're in the war room. Someone's explaining their elaborate branch-switching workflow involving three stashes, a WIP commit, and something they call "the prayer."
That's when Jake, our most junior developer, asks: "Why don't we just use worktrees?"
Silence.
"You know, git worktree
. It's like having multiple checkouts but they share the same Git objects."
More silence.
"I used it for my side project..."
First Attempt: Proof of Insanity
# Jake's demo
cd megacorp-app
git worktree add ../megacorp-hotfix hotfix/payment-bug
# Everyone: "Wait, that's it?"
cd ../megacorp-hotfix
# Full checkout. No stashing. No commits. No conflicts.
# My changes still safe in the other directory.
Output: Preparing worktree (checking out 'hotfix/payment-bug')
Time elapsed: 3 seconds
The "Wait, What?" Moment
We ran the numbers:
- Full clone: 12 minutes
- Worktree creation: 3 seconds
- Disk space for second clone: 400GB
- Disk space for worktree: 2.1GB (just the working files)
The worktree was using the same .git
objects. No duplication. Full Git functionality. Instant switching.
Building the Production Monstrosity
Version 1: Held Together With String
Our first attempt was naive:
#!/bin/bash
# worktree-switch.sh v1
BRANCH=$1
WORKTREE_DIR="../megacorp-$BRANCH"
git worktree add "$WORKTREE_DIR" "$BRANCH"
cd "$WORKTREE_DIR"
Failed spectacularly when someone tried to check out feature/updates/2023/q4/final-final-v2-actually-final
.
Version 2: Addressing the Obvious Problems
#!/bin/bash
# worktree-manager.sh v2
BRANCH=$1
SAFE_NAME=$(echo "$BRANCH" | sed 's/\//-/g' | cut -c1-50)
WORKTREE_BASE="$HOME/worktrees/megacorp"
WORKTREE_DIR="$WORKTREE_BASE/$SAFE_NAME"
# Check if already exists
if [ -d "$WORKTREE_DIR" ]; then
echo "Worktree exists, switching to it..."
cd "$WORKTREE_DIR"
exit 0
fi
# Create new worktree
git worktree add "$WORKTREE_DIR" "$BRANCH" || {
# Branch doesn't exist, create it
git worktree add -b "$BRANCH" "$WORKTREE_DIR"
}
cd "$WORKTREE_DIR"
This worked until someone created 47 worktrees and forgot about them.
Version 3: The Thing That's Still Running
#!/bin/bash
# wt (because we're too lazy to type worktree)
# 18 months in production and counting
BRANCH=${1:-$(git branch --show-current)}
WORKTREE_BASE="$HOME/worktrees/$(basename $(git rev-parse --show-toplevel))"
SAFE_NAME=$(echo "$BRANCH" | sed 's/\//-/g' | cut -c1-50)
WORKTREE_DIR="$WORKTREE_BASE/$SAFE_NAME"
# Prune dead worktrees first
git worktree prune
# List existing worktrees with this branch
EXISTING=$(git worktree list --porcelain | grep -B2 "branch refs/heads/$BRANCH" | grep worktree | cut -d' ' -f2)
if [ -n "$EXISTING" ]; then
echo "→ Switching to existing worktree"
cd "$EXISTING"
exec $SHELL
fi
# Create new worktree
mkdir -p "$WORKTREE_BASE"
echo "→ Creating new worktree for $BRANCH"
if git show-ref --verify --quiet "refs/heads/$BRANCH"; then
git worktree add "$WORKTREE_DIR" "$BRANCH"
else
git worktree add -b "$BRANCH" "$WORKTREE_DIR"
fi
# Setup hooks
cp .git/hooks/* "$WORKTREE_DIR/.git" 2>/dev/null || true
# Install dependencies if needed
cd "$WORKTREE_DIR"
if [ -f package.json ]; then
echo "→ Installing dependencies..."
npm ci --silent
fi
exec $SHELL
Then we added the secret sauce—automatic cleanup:
# In everyone's .zshrc/.bashrc
alias wtclean='git worktree list | grep -v "$(pwd)" | cut -d" " -f1 | \
xargs -I {} sh -c "[ ! -d {} ] && echo Removing {} && git worktree remove {}" 2>/dev/null'
# Cron job that runs nightly
0 2 * * * find ~/worktrees -mindepth 2 -maxdepth 2 -type d -mtime +30 \
-exec sh -c 'cd {} && git worktree remove {} --force' \; 2>/dev/null
The Numbers Don't Lie
After 18 months, here's what our metrics look like:
Performance
Disk Usage
Reliability
The Unexpected Benefits
# Run tests on multiple branches simultaneously
for branch in feature/auth feature/payments feature/ui; do
(cd ~/worktrees/megacorp/$branch && npm test) &
done
wait
# Reviewing PRs became trivial
wt feature/colleague-branch
# Full IDE, full build, full context. No stashing required.
# Compare performance between branches
cd ~/worktrees/megacorp/main && npm run benchmark > main.bench
cd ~/worktrees/megacorp/feature-optimization && npm run benchmark > feature.bench
diff main.bench feature.bench
Why This Actually Makes Sense
Turns out Git was designed for this all along. We just never noticed.
The Hidden Architecture of Git Worktrees
Git stores all objects in .git/objects
. Worktrees don't duplicate this—they create a lightweight .git
file pointing to the main repository:
# In a worktree's .git file:
gitdir: /home/dev/megacorp/.git/worktrees/feature-auth
This means:
What Traditional Workflows Get Wrong
The stash/checkout dance assumes your work is interruptible. It's not. You're holding 47 things in your head, and git stash
just added number 48.
Multiple clones solve the wrong problem. You don't need multiple repositories—you need multiple working directories sharing the same repository.
The Pattern: Tools Already Solve Problems We Haven't Recognized
This isn't just about Git worktrees. It's about recognizing that:
Other Places This Thinking Applies
Try This In Your Architecture
Before you implement that complex branch management strategy, ask:
The Worktree Checklist
If you answered yes to any of these, you need worktrees.
Your Mission
# Right now. Don't overthink it.
git worktree add ../testing-worktrees main
cd ../testing-worktrees
# Notice how all your Git commands just work
git log --oneline -10
git remote -v
git status
# Create another one
git worktree add ../another-test feature/your-branch
# List them all
git worktree list
# Be amazed that this was always there
The Confession
Is using Git worktrees for everything a best practice? The Git documentation barely mentions them.
Has it eliminated context-switching friction for 14 developers for 18 months? Absolutely.
Would I go back to the stash/checkout dance? Looks at the 48 minutes saved per day. Looks at zero merge conflicts from stash pops.
Not even if you paid me.
P.S. - Three months after implementing this, Jake (remember Jake?) used the time saved to build our entire deployment pipeline using Git hooks and hard links. That's a story for next week.
What Git features are you not using because nobody told you they existed? What's your most successful "I can't believe this was here all along" discovery?
Next week: How we replaced our entire deployment pipeline with creative abuse of symbolic links and Git hooks. Spoiler: It's 100x faster than our previous "proper" solution.