How I Use Git Worktrees
There are a bunch of posts on the internet about using git worktree
command. As far as I can tell,
most of them are primarily about using worktrees as a replacement of, or a supplement to git
branches. Instead of switching branches, you just change directories. This is also how I originally
had used worktrees, but that didn’t stick, and I abandoned them. But recently worktrees grew
on me, though my new use-case is unlike branching.
When a Branch is Enough
If you use worktrees as a replacement for branching, that’s great, no need to change anything! But let me start with explaining why that workflow isn’t for me.
The principal problem with using branches is that it’s hard to context switch in the middle of doing something. You have your branch, your commit, a bunch of changes in the work tree, some of them might be stages and some unstaged. You can’t really tell Git “save all this context and restore it later.” The solution that Git suggests here is to use stashing, but that’s awkward, as it is too easy to get lost when stashing several things at the same time, and then applying the stash on top of the wrong branch.
Managing Git state became much easier for me when I realized that the staging area and the stash are just bad features, and life is easier if I avoid them. Instead, I just commit whatever and deal with it later. So, when I need to switch a branch in the middle of things, what I do is, basically:
And, to switch back,
To make this more streamlined, I have a ggc
utility which does “commit all with a trivial message”
atomically.
And I don’t always reset HEAD~
— I usually just continue hacking with .
in my Git log and then amend the commit
once I am satisfied with subset of changes
So that’s how I deal with switching branches. But why worktrees then?
Worktree Per Concurrent Activity
It’s a bit hard to describe, but:
- I have a fixed number of worktrees (5, to be exact)
- worktrees are mostly uncorrelated to branches
- but instead correspond to my concurrent activities during coding.
Specifically:
-
The main worktree is a readonly worktree that contains a recent snapshot of the remote main branch. I use this tree to compare the code I am currently working on and/or reviewing with the master version (this includes things like “how long the build takes”, “what is the behavior of this test” and the like, so not just the actual source code).
-
The work worktree, where I write most of the code. I often need to write new code and compare it with old code at the same time. But can’t actually work on two different things in parallel. That’s why
main
andwork
are different worktrees, butwork
also constantly switches branches. -
The review worktree, where I checkout code for code review. While I can’t review code and write code at the same time, there is one thing I am implementing, and one thing I am reviewing, but the review and implementation proceed concurrently.
-
Then, there’s the fuzz tree, where I run long-running fuzzing jobs for the code I am actively working on. My overall idealized feature workflow looks like this:
This is again concurrent: I can hack on the branch while the fuzzer tests the “same” code. Note that it is crucial that the fuzzing tree operates in the detached head state (
-d
flag forgit
switch
). In general,-d
is very helpful with this style of worktree work. I am also sympathetic to the argument that, like the staging area and the stash, Git branches are a misfeature, but I haven’t made the plunge personally yet. -
Finally, the last tree I have is scratch – this is a tree for arbitrary random things I need to do while working on something else. For example, if I am working on
matklad/my-feature
inwork
, and reviewing#6292
inreview
, and, while reviewing, notice a tiny unrelated typo, the PR for that typo is quickly prepped in thescratch
worktree:
TL;DR: consider using worktrees not as a replacement for branches, but as a means to manage concurrency in your tasks. My level of concurrency is:
-
main
for looking at the pristine code, -
work
for looking at my code, -
review
for looking at someone else’s code, -
fuzz
for my computer to look at my code, -
scratch
for everything else!