Git Worktrees Part 2
Once you start using worktrees, the immediate problem becomes obvious: you have multiple branches evolving independently, and they all need to stay synchronized with your main branch. When a hotfix lands in production or someone merges a feature that touches the same code you’re working on, every active worktree potentially needs updating. Do this wrong and you’ll spend your afternoon resolving merge conflicts. Do it right and the synchronization becomes almost invisible.
The key insight is that worktrees share a single git database, which means operations in one worktree affect all the others instantly. When you fetch new commits, they’re immediately available everywhere. When you create a branch, it shows up in every worktree. This shared state is what makes synchronization practical, but it also requires understanding which operations are local to a worktree and which are global to the repository.
Keeping Your Base Branches Fresh
The most common synchronization task is pulling updates from your remote repository into your local branches. The naive approach—running git pull
in every worktree—works but creates unnecessary complexity. A better pattern treats one worktree as the source of truth for your base branches.
Start by designating your main
worktree as the place where you update shared branches. When you want to sync with the remote, navigate there and pull:
Because all worktrees share the same git database, this updates the main branch everywhere. Your other worktrees don’t automatically check out the new commits, but the branch itself is updated. When you’re ready to integrate those changes into a feature branch, switch to that worktree and rebase:
cd ../feature
git rebase main
This rebases your current branch onto the newly updated main. If you’ve been working on the new-auth-system
branch in this worktree, the rebase incorporates all the latest main branch commits beneath your feature work.
The reason this works cleanly is that rebasing is a per-branch operation, not a per-worktree operation. You’re not moving the worktree itself—you’re updating the branch that happens to be checked out there. Other worktrees tracking different branches are unaffected until you explicitly rebase them too.
Handling Conflicts Across Worktrees
When you rebase a branch in one worktree and hit conflicts, git stops and waits for you to resolve them. This happens entirely within that worktree’s context. Your other worktrees continue to work normally because they’re tracking different branches, and git’s conflict state is worktree-specific.
Here’s where the isolation becomes valuable. If you’re mid-rebase in your feature worktree and get pulled into an urgent code review, you can just cd ../review
and handle the review in a completely clean environment. The conflicted rebase in the feature worktree sits there waiting for you, exactly as you left it. When you return to resolve the conflicts, the process is standard git conflict resolution—fix the conflicting files, stage them, and continue the rebase:
cd feature
# Fix conflicts in the files git identified
git add .
git rebase --continue
Once the rebase completes, that branch is synchronized with main. Your other feature branches in other worktrees still need their own rebases when you’re ready to update them.
Integration Testing Across Features
A powerful pattern enabled by worktrees is testing how multiple in-progress features work together before merging any of them. Create a dedicated integration worktree that exists purely for combining and testing branches:
git worktree add integration -b integration main
This creates a branch called integration
based on main. Now you can merge multiple feature branches into it:
cd integration
git merge feature-auth
git merge feature-billing
git merge feature-notifications
npm test
This tests how all three features interact without touching any of the individual feature branches. If tests pass, you know the features are compatible. If tests fail, you know there’s an integration problem to solve before merging to main.
The integration branch is disposable. After testing, you can delete it and recreate it fresh the next time you need integration testing. The pattern works because your feature branches remain untouched—you’re only testing a temporary combination.
When you’re satisfied that features work together, merge them to main individually:
cd ../main
git merge feature-auth
git push origin main
git merge feature-billing
git push origin main
Each merge is a deliberate, tested step. This approach catches integration issues early while keeping your main branch clean and your feature branches focused.
Cleaning Up Finished Work
As you complete features and merge branches, worktrees accumulate. Some track branches that no longer exist. Others were created for one-time tasks and are no longer needed. Regular cleanup prevents your workspace from becoming cluttered with obsolete directories.
When you’re done with a worktree, remove it:
git worktree remove feature
This deletes the working directory and unregisters the worktree. The branch itself remains in your repository—you’ve only removed the working directory where it was checked out. If the branch is also finished and merged, delete it separately:
git branch -d new-auth-system
The -d
flag is safe because git prevents deleting unmerged branches. If you’re certain you want to delete an unmerged branch, use -D
instead.
Sometimes you’ll manually delete a worktree directory without using git worktree remove
. Maybe you cleaned up your filesystem and forgot to tell git. When this happens, git still thinks the worktree exists. Clean up the stale metadata:
git worktree prune
This removes references to worktrees whose directories no longer exist. Running this periodically keeps your worktree list accurate.
The Daily Rhythm
After using worktrees for a while, a natural workflow emerges. Start your day by updating main in the main worktree. Rebase your active feature branches to incorporate those changes. When new work comes in, create feature branches in dedicated worktrees. When you need to review code, use your review worktree. When you finish work, merge to main and clean up the worktree.
This rhythm eliminates the constant branch switching that fragments your attention. Each workspace maintains its own context. You move between them by changing directories, and your mental context switches cleanly because the filesystem itself shows you where you are.
The mechanics are simple, but the effect is profound. You stop thinking about git as a sequence of checkouts and stashes and start thinking about it as a set of parallel workspaces. Each workspace evolves independently until you decide to synchronize them. The synchronization itself becomes a deliberate action rather than an automatic side effect of switching branches.
That deliberateness is the real benefit. You control when contexts merge, when branches update, and when work moves between worktrees. Git stops interrupting your flow and starts supporting it.
Leave a Reply