How I Use Git Worktrees (2024)

oogali1 pts0 comments

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,<br>most of them are primarily about using worktrees as a replacement of, or a supplement to git<br>branches. Instead of switching branches, you just change directories. This is also how I originally<br>had used worktrees, but that didn’t stick, and I abandoned them. But recently worktrees grew<br>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<br>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<br>something. You have your branch, your commit, a bunch of changes in the work tree, some of them<br>might be stages and some unstaged. You can’t really tell Git “save all this context and restore it<br>later.” The solution that Git suggests here is to use stashing, but that’s awkward, as it is too<br>easy to get lost when stashing several things at the same time, and then applying the stash on top<br>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<br>features, and life is easier if I avoid them. Instead, I just commit whatever and deal with<br>it later. So, when I need to switch a branch in the middle of things, what I do is, basically:

$ git add .<br>$ git commit -m.<br>$ git switch another-branch

And, to switch back,

$ git switch -

# Undo the last commit, but keep its changes in the working tree<br>$ git reset HEAD~

To make this more streamlined, I have a ggc utility which does “commit all with a trivial message”<br>atomically.

Reminder: Git is not a version control system, Git is a toolbox for building a VCS. Do have a<br>low-friction way to add your own scripts for common git operations.

And I don’t always reset HEAD~ — I usually just continue hacking with . in my Git log and then amend the commit<br>once I am satisfied with subset of changes

Reminder: magit, for Emacs and VS Code, is<br>excellent for making such commit surgery easy. In particular, instant fixup is excellent. Even<br>if you don’t use magit, you should have an equivalent of instant fixup among your Git scripts.

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<br>branch. I use this tree to compare the code I am currently working on and/or reviewing with the<br>master version (this includes things like “how long the build takes”, “what is the behavior of<br>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<br>with old code at the same time. But can’t actually work on two different things in parallel.<br>That’s why main and work are different worktrees, but work also constantly switches branches.

The review worktree, where I checkout code for code review. While I can’t review code and write<br>code at the same time, there is one thing I am implementing, and one thing I am reviewing, but the<br>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<br>on. My overall idealized feature workflow looks like this:

# go to the `work` worktree<br>$ cd ~/projects/tigerbeetle/work

# Create a new branch. As we work with a centralized repo,<br># rather than personal forks, I tend to prefix my branch names<br># with `matklad/`<br>$ git switch -c matklad/awesome-feature

# Start with a reasonably clean slate.<br># In reality, I have yet another script to start a branch off<br># fresh from the main remote, but this reset is a good enough approximation.<br>$ git reset --hard origin/main

# For more complicated features, I start with an empty commit<br># and write the commit message _first_, before starting the work.<br># That's a good way to collect your thoughts and discover dead<br># ends more gracefully than hitting a brick wall coding at 80 WPM.<br>$ git commit --allow-empty

# Hack furiously writing throughway code.<br>$ code .

# At this point, I have something that I hope works<br># but would be embarrassed to share with anyone!<br># So that's the good place to kick off fuzzing.

# First, I commit everything so far.<br># Remember, I have `ggc` one liner for this:<br>$ git add . && git commit -m.

# Now I go to my `fuzz` worktree and kick off fuzzing.<br># I usually split screen here.<br># On the left, I copy the current commit hash.<br># On the right, I switch to the fuzzing worktree,<br># switch to the copied commit, and start fuzzing:

$ git add . && git commit -m. |<br>$ git...

commit code worktrees worktree branch work

Related Articles