Git Worktrees: The Best Git Feature You’ve Never Heard Of

James Pulec
Level Up Coding
Published in
6 min readApr 28, 2020

--

Before There Were Trees

When I first started using git professionally, one habit I had was to frequently stash code. I’d be working on a particular feature and I’d see a Hipchat alert (yes, this was before Slack) about an error being thrown in production. I’d take a look at the stacktrace in Sentry, and find the offending section of code. From there I’d return to my terminal, stash my changes, open a new bugfix branch off master, and begin trying to reproduce the error locally.

Usually this process would happen without too much pain. If there were untracked files in my working tree, I’d just add them and stash everything. After I’d pushed a bugfix branch up to Github, I’d unstash my changes and unstage any of the original untracked files I had added. Occasionally I’d make a mistake and do something awful like accidentally trashing my untracked files, or uncommitted changes.

Other times, I’d be working on a feature, and I’d see a PR review request from one of my coworkers. I generally try to review code as fast as possible, and depending on how urgent the change was, I would stop what I was working on to review the changes. Again, this meant stashing my changes and pulling down the code locally to try out the new changes, and verify edge case behavior.

Eventually, I got frustrated with the pain of stashing and unstashing, and decided that I should just clone the repository into another directory, and I would use that copy of the repository for bugfixes and code reviews. And this mostly worked. However, it isn’t without its downsides.

One big issue with having multiple copies of a repo in different directories is that branches aren’t shared between them. Sure, you can set one directory to be a remote of the other, but it’s still painful to constantly be syncing branches. In practice, if you use a development model where all code gets pushed to a central repository like Github, this isn’t as painful. Similarly, if you use stashing a lot, as I did, it takes a tiny bit of work to apply a stash from my directory to the other as a patch.

Another issue is that each directory has its own .git directory. This means all sorts of configuration has to be duplicated. I use a fair number of git pre-commit hooks, so these now needed to exist in both directories. Again, there are ways to work around this, such as using symlinks, or possibly centralizing some of this config in your home folder’s .gitconfig, but it’s not ideal.

Finally, this process didn’t easily scale beyond the 2 concurrent repo case. When I found myself needing to halt my existing work, create a bugfix, and review a coworkers code all at the same time, I was faced with the prospect of cloning another copy of the repo. Cloning the repo wasn’t exactly quick, and again I was faced with the burden of copying and keeping in sync all the configuration I had for the existing two repositories.

Enter Git Worktrees

Git Worktrees are a feature that allow you to have a single repository with multiple checked out working branches at the same time. Now that may not sound that cool, but let me lay out an example.

I have a copy of the ApertureScience repository located at /home/James/Aperture. I’m going to begin working on a new feature, cake. Before I start this work, I’m first going to run git worktree add cake. This will create a new directory for me to do my work located at /home/James/Aperture/cake. I spend a few hours working on my cake feature.

A wild error appears in our production app. Probably a rogue test subject causing trouble… Better go deal with that.

I stop working on the cake feature and run the command git worktree add bugfix inside the /home/James/Aperture directory. This almost instantly creates a new working copy of the repository at /home/James/Aperture/bugfix on a new branch called bugfix. I cd into that directory and run whatever commands I need to boot up the application. Once I discover the bug and figure out what changes need to be made to fix, I commit the changes, push up my branch for review, and then return to the feature work I was doing on /home/James/Aperture/cake. No stashing hassle, and no worrying that my most recent pre-commit hooks aren’t running.

Effectively, you can work on as many things in parallel as you’d like and can feel free to just pause your work and come back later.

Why aren’t more people talking about Git Worktrees?? They’re incredible!

Can You Really Have Your Cake and Bugfix Branches Too?

Of course, there are some downsides to be aware of when using git worktrees.

If you’re not stashing your code, or being forced to commit it, you’re more likely to just leave code in a working state. Usually, this is fine, but it means that you may not have any refs in the reflog if something goes wrong, or you may be less likely to have a committed copy of your code somewhere if you suffer a disk failure.

It’s possible that being able to work on more things at once may also be detrimental to your productivity. You might wind up splitting your focus and try to do too many things in parallel.

Finally, some tools may not fully support worktrees well. Worktrees rely on the fact that git allows .git to be a file that points to a directory, instead of an actual directory. Some tools incorrectly assume that .git must be directory and run into issues with worktrees.

Recommended Setup

I tend to try to keep things tidy, so I have my worktrees setup in a specific fashion to allow me to work easily. My worktrees folder looks like the following:

A nearly accurate representation of my worktree setup

/home/James/worktrees/.bare — This is the actual git repository, a bare instance. This stores only the repo metadata and has no actual working tree. I keep this in a separate hidden directory so that I can create new worktrees from this folder, but don’t accidentally try to check out a branch in the worktrees directory, which is an operation that can only be done in a working tree.

/home/James/worktree/.git — This file just points to the .bare directory.

/home/James/worktree/master — I always keep a copy of master, mostly so that if something comes up I can spin up a local copy of whatever is running in production.

/home/James/worktree/hotfix — This branch is kept as a place where I perform hotfixes. It happens with enough frequency that I keep a dedicated worktree around for it.

/home/James/worktree/<feature> — I have a number of different feature worktrees, which are divided by particular feature areas of the codebase.

I’ve tried a couple different schemes for how to organize these branches, and still think there’s room for improvement, but am fairly happy with having at least a master worktree that allows easily spinning up exactly what is live on master, and a number of feature branches that allow me to work on multiple things in parallel. I also like to keep around worktrees such as, dependencies where I can drop in, bump a dependency, and spend a few minutes working on getting our code base to work with a new version of the dependency.

As a bonus, if you use Docker-Compose, or any other tool that is isolated by directory path, you can have multiple copies of your application running at the same time. Docker-Compose relies on using it’s containing folder for as a name for the containers that are created and for managing running containers. Of course, to make this work, you need to make sure your application is able to properly isolate itself with dynamic port allocation, etc.

All in all, this style of working has made it so much easier for me to context switch when it is necessary, and provides me with small chunks of work that I can get done in less than 1 hour increments.

I consider git worktrees to be one of the secret weapons in my toolbox and could not imagine going back to developing without them.

--

--