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.
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.
# 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)
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
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..."
# 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
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.
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
.
#!/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.
#!/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
After 18 months, here's what our metrics look like:
- Context switch time before: 2-5 minutes (stash, checkout, npm install)
- Context switch time after: 3 seconds
- Daily context switches per developer: ~12
- Time saved per developer per day: 48 minutes
- Traditional approach (3 clones): 1.2TB per developer
- Worktree approach (main + 5 active): 410GB per developer
- Shared Git objects: 398GB (counted only once)
- Storage saved: 66%
- Stash conflicts per week: 14-20
- WIP commits accidentally pushed: 3-5 per month
- Lost work from bad stash pops: "It happens"
- Issues with worktree approach: 0
- Parallel CI Testing
# Run tests on multiple branches simultaneously
for branch in feature/auth feature/payments feature/ui; do
(cd ~/worktrees/megacorp/$branch && npm test) &
done
wait
- Instant PR Reviews
# Reviewing PRs became trivial
wt feature/colleague-branch
# Full IDE, full build, full context. No stashing required.
- A/B Testing Performance
# 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
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:
- All worktrees share the same object database
- Commits in any worktree are immediately visible to all others
- Branches can't be checked out in multiple places (Git prevents conflicts)
- Perfect cache utilization (objects loaded once, used everywhere)
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.
This isn't just about Git worktrees. It's about recognizing that:
- Built-in features often outperform third-party solutions
- The "obvious" workflow might be the wrong workflow
- Junior developers sometimes see solutions seniors miss
- Using SQLite's WAL mode as a message queue (we do this too)
- Treating nginx as a programmable CDN
- DNS as a service discovery mechanism
- File system hard links as a deployment strategy
Before you implement that complex branch management strategy, ask:
- What does Git already provide?
- Why isn't everyone using it? (Usually: they don't know it exists)
- What would happen if you used it as your primary workflow?
- Is your repo over 1GB?
- Do developers switch contexts more than twice daily?
- Are you using stash/WIP commits as a workflow?
- Do you have disk space for exactly one more copy of working files?
If you answered yes to any of these, you need worktrees.
# 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
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.