This post is about the awesome git worktree, that simplified my personal workflow with git very much. In the past I had at least 2 versions of repository checked out locally or fiddled around with git stash to find a thing I did weeks ago. But let’s talk about how I use git and how git worktree helped me.

Use Cases

When I’m reviewing a Pull or Merge Request, sometimes I would like to see the code in my own IDE instead of the GitLab or GitHub UI. Normally I have a single clone of a repository on my local machine and I’m in the process of adding new features or fixing a bug myself. So I don’t want to commit things just yet, but just check out another branch. I know I could use git stash or create a WIP commit and then amend by providing a good commit message later, but almost all the time I forget about my existing stash or the earlier WIP commit.

Another time I’m starting to work on an improvement or simply experimenting with an implementation detail and therefore also don’t want to commit anything just yet.

At last, I want to update one of my branches with the current state of the main branch or want to verify that something is working or not working on the current main branch in comparison to my branch.

Working with worktree

The solution to all of these problems is to use git worktree and have multiple branches checkout out locally at the same time.

To be as flexible as possible I tend to use the following structure for all repositories where I think I’ll need multiple branches checkout out at the same time:

➜  upstream-watch pwd
/home/aps/Code/github/upstream-watch
➜  upstream-watch ls -l
total 8
drwxr-xr-x 5 aps aps 4096 Jan 11 08:27 my-feature-1
drwxr-xr-x 6 aps aps 4096 Jan 11 08:26 upstream-watch-main

So I got a <repo-name>--main for the main branch and additional branches was <repo-name>--xxx.

Another feature you get with this is you get a clean state and can open your IDE in a new folder. This could be annoying if you have a lot of custom configuration (like local .env files, which are ignored by git), but for most of the changes I don’t need these.

The main clone of the repository is always repository-name-main. To not pollute my local file structure I tend to have each project in a dedicated directory under my ~/Code folder.

To create a new worktree checkout you simply have to use git worktree add -b my-feature-1 ../my-feature-1. If you don’t want to create a new branch then simply skip the -b my-feature-1 from the command above and use it like this git worktree add ../my-feature-1.

You can also list all your existing worktrees with from any worktree of the cloned repository:

➜  my-feature-1 git:(my-feature-1) git worktree list
/home/aps/Code/github/upstream-watch/upstream-watch-main  c477b51 [main]
/home/aps/Code/github/upstream-watch/my-feature-1         c477b51 [my-feature-1]

I’m lazy, so I’m mostly using this very simple wrapper script to create new worktrees. Then I’ll simply call cw my-feature, when you store the scrip below as cw (short for create worktree) in your local bin folder.

#!/bin/bash
set -euo pipefail

name=$1

git worktree add ../$name -b $name

printf "Created worktree at:  %s \n" "../$name"

Deleting a worktree is as simple as git worktree remove ../my-feature-1.

This also enables the usage of git cherry-pick between multiple worktrees, which is very neat when you find a bug while working on another task. Simply commit it on your current working branch and pick it into a fix-bug-xxx branch afterwards and create a dedicated MR/PR for this fix.

Conclusion

git worktree simplified my personal workflow with git and saves some (not so expensive anymore) space on my local disk, as I don’t need multiple versions of a repository on my local machine. I know worktree is a rather old git feature, but nevertheless it is very useful and I know a lot of people that are not using it (yet).