My Git Worktrees Workflow


tldr;

git worktrees rock! Use git bare repos along with git worktrees to maintain a clean worktree structure. Jump to the bottom of this article to find details on my exact setup.

what are git worktrees?

Git worktrees, put simply, allow you to check out multiple branches within a git repo at the same time into separate directories. This can be useful when you need to jump between branch to branch quickly & frequently.

preamble

A normal work day for me usually includes fixing a bug or adding a feature on a git branch. Another normal occurrence for me is performing code reviews for peers. I usually like to check out the code I’m reviewing so I can test locally to verify behavior correctness. While I’m reviewing I may notice a bug/unexpected behavior I’ll want to verify doesn’t exist in the trunk (e.g. develop/main) branch to determine if this behavior was introduced in this patch or not.

Already in the scenario above I’ve had to hop branches twice! In a traditional non git worktrees workflow I would have had to stashed/committed/reverted any uncommitted changes on each branch.

What I’ve come to realize is the additional overhead of having to think of what to do with my uncommitted code when needing to switch branches was yet another slow-down/barrier when needing to switch branches.

Additionally, when I would need to switch to reviewing a branch (or several) after working on my own feature branch, there was always that moment when needing to return to my own work and remember which git branch I was working on.

what problems am i trying to solve?

Here are my two pain-points:

  1. Switching branches requires mental overhead of having to deal with committed changes.
  2. Switching back and forth between branches requires the additional step of having to remember what branch you were working on and checking it out again.

a better use of worktrees

So how does my workflow with git worktrees help with these two points?

Well, since each worktree is just a git branch checked out into its own directory we can have uncommitted changes in each worktree and easily just switch directories in our editor/IDE/terminal. Using worktrees resolves my pain-point #1.

In theory we could create a worktree per git branch, but that approach still leaves me with pain-point #2. My preference is not to automatically create a new worktree per branch, but rather create permanent worktrees that I can use to checkout branches in.

The permanent worktrees I create for most projects are:

  • main (always checked out on my trunk branch (e.g. main/develop))
  • work (will checkout whatever branch I’m currently working on here)
  • review (reserved for whatever PR branch I’m currently reviewing)

This setup allows me to checkout a branch on my work worktree, switch to my review worktree to do PR reviews, and quickly switch back to my work worktree without having to remember what branch I was last working on.

This is my highly specific example of how I use worktrees, but hopefully it illustrates the usefulness of them. I’ve had times were I’ve needed to write up a proof of concept where I’ll work on it throughout the day between my feature work and meetings, but I don’t have anything meaningful to commit. In a case like this, I will create a temporary worktree named something like x-feature-poc so that I can easily jump to that worktree throughout the day.

tip: use git worktrees in a bare repo

My preferred approach to managing worktrees is to clone the git repo I’m working on as a bare repo so that my git files are clearly separated from my working files. I plan on writing an article at some point to outline the different approaches to using worktrees and why I prefer this one. I’ll be sure to update this article once I do that write-up!

So without further ado, here’s my setup.

  1. Create a folder for your project. For this example I’ll use the folder name myApp
  2. Inside your project folder (myApp in our case), clone your git repo as a bare repo: git clone --bare https://yourgitserver/myApp.git
  3. Verify that git has cloned a folder named myApp.git into your myApp folder that contains all of your git files with no working files.
  4. Create a new directory in myApp named worktrees. We’ll use this directory to store all of our worktrees. Your directory structure should now look something like this:
  1. Now within myApp.git, run git worktree add ../worktrees/main.

That’s it! You’ve just created a worktree named main in your worktrees folder that checked out a branch named main!

I mentioned before that most of the projects have 3 permanent worktrees. This is an example of what my folder structure looks like:

The one thing to note is anytime you want to manage your worktrees (delete/add new ones) you’ll need to do so from within the myApp.git directory.

issues with using git bare repos

Unfortunately the bare repo approach leaves us with two issues we’ll need to address:

  1. bare repos do not automatically setup tracking of remote branches on checkout.
  2. remote.origin.fetch is not set when cloning a bare repo so we’ll need to set this ourselves.

Below are my solutions to both of these problems.

bare repo branch tracking

To fix the tracking issue I’ve created git alias to make updating the tracking on your git worktrees easier!

$ git config --global alias.track '!git branch --set-upstream-to=origin/$(git branch --show-current)'

Now you should be able to run git track anytime you need to update the tracking on your git branches!

remote.origin.fetch not set

To fix the issue with the remote.origin.fetch not being set just run the following command in your git files directory:

$ git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"