My Git Worktrees Workflow


tldr;

I use git bare repos along with git worktrees to maintain a clean worktree structure. Jump down to the bottom of this article to see details on my exact setup and solutions to gotchas around using a bare repo in this manner!

what are git worktrees?

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

what problems am i trying to solve?

On any given day I’ll be fixing a bug or adding a feature on my own git branch when a coworker asks me to review their pull request. A part of my review process includes checking out and running the proposed branch locally to verify behavior correctness. During this review I may notice a bug/unexpected behavior when testing their branch in which I’ll want to verify this bug does not exist in the trunk (e.g. develop/main) branch to determine if this behavior was introduced in this patch or not.

Already in this example I’ve had to hop branches twice, and along the way 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 and 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.

We can reduce my two pain-points to:

  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.

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 point-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.

my preferred setup for git worktrees

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:
myApp
├── myApp.git
└── worktrees
  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:

myApp
├── myApp.git
└── worktrees
    ├── main
    ├── review
    └── work

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/*"