Why You Should Be Using Git Worktrees

 It's no surprise to anyone reading this, that git is the standard for source control today. It has completely replaced SVN, Mercurial, and many other systems we're glad to leave in the past, becoming without doubt the industry standard source control system. Maybe someday it will be superceded by jiujitsu or something even fancier, but for now, git is here to stay. It's also not surprising to say that people are moreso interested in programming as opposed to source control management systems, so naturally people often learn just enough git to make it get out of the way for them, and little else. There's a number of features that are criminally underused for this reason. The main ones I'm thinking of are git bisect, staging hunks with git add -p, and the topic of today's post, worktrees.

 Worktrees are branches on steroids. However, unlike steroids (or some other git features), worktrees are not anger inducing. They are in fact almost a drop-in replacement for branches, which is one of the few git features you can be certain anyone using a source control system is familiar with. It's best to get this kind of information straight from the source, but as a brief explanation (i.e. just enough working knowledge to understand this post), they are simply a means of checking out a new branch in a separate directory. As a horrible pseudo-equivalence, you can imagine git worktree add ../<branch name> <branch name> to be roughly equivalent to git clone . ../<branch name> && cd ../<branch name> && git checkout -b <branch name>.

 The reason this is useful, it that it means you can work on multiple branches concurrently with a much lower amount of effort to switch contexts.

 I can already hear the cries of detraction: Context switching is the devil! Just keep your working directory clean! This is already solved by <some bloated GUI>!

 Well these complaints just aren't practical. You often need to leave your current state of a repo dirty. Sometimes the different technology doesn't fit your workflow, and while nobody wants to context switch frequently, it's an inevitability in the workplace. You can't simply sit around with one open pull request being worked on and do nothing else until you get feedback on it! Given that you are going to be context switching, and that worktrees make it much less painless. Let's get into some examples to show this.

Demo

0:00
/
A live demo of how easy it is to use worktrees (I'll show you the gwc script at the end!)

Examples

 Demo Meeting

  It's time for you to show off and get feedback on the features you've been working on! You arrange a meeting with some colleagues and you're ready to go. Are you going to demo the cool new features from some branch you had to messily octopus merge together? Maybe instead you plan to stop the demo halfway through so you can checkout another branch, set up the environment correctly, and then begin? Well with a worktree based workflow, you can spend 30 seconds before the meeting starts, and run each feature you want to demo on a server on a different port! Now when you're moving from one feature to another, you simply open another tab! Easy!

 Bug Squashing

  The new feature has been released, and customers are loving it. Unfortunately, they're expressing their love in a funny way - by pointing out many small UI inconsistencies... "These buttons are out of alignment!", "When I have 5,000 options in this dropdown, the search is slow!", you know the drill.

 Your options are:

  • Bundling all these fixes into one PR, slowing down the deployment of every change you're making and frustrating the review process
  • Checking out a new branch for each bug fix, and when you (inevitably) read the reviews showing you made a typo, or that there was a linting error, you have to stop your current work in progress, stash the changes, check out a different branch, make the quick fix, push the change, switch back to your WIP branch, pop your stash....

 If you were using worktrees however, you can just open the file in another directory to make a quick fix and push the change without ever having to interfere with your current working state.

 Proof of Concepts

  You have a great idea for a new feature, but you want to experiment with it before sharing with a wider audience. It's low priority, maybe just for fun, and because of that you can only really work on it in downtime or when blocked. Instead of you forgetting that you ever checked out a branch to do this investigation, and never opening it again, or having 25 different unnamed stashes in the stack, you can see every time you look at your Code/ directory that it's right there. Waiting for you in the exact same state as when you left it.

 Comparisons & Benchmarks

  Suppose you're writing a new API endpoint and you want to run some benchmarks. You can quickly and easily run two instances of your backend simultaneously and just flip the port to compare the implementations. No need to git clone the repo to another location and disrupt your workflow. This is immediately possible with worktrees.

Limitations

 However, there is one minor limitation and it is related to dependencies. I'm hesitant to really call it a limitation, but I want to get ahead of the git branch diehards and address it. Very likely, most of your branches don't end up requiring new dependencies. That means you don't need to install anything new as you're still operating within the same directory. However, since worktrees are actually completely separate directories, you do have to install all your dependencies in each new worktree. Depending on your tooling and overall environment, this mightn't be too big of a deal. For example, with bun and uv, most of my installs are extremely fast. So fast that this limitation doesn't really affect me.

 On the positive side of this coin though, you have the benefit of being able to try out new runtimes, major version changes, etc without any risk to your primary worktree.

 I'm sure that there is a smart way to bypass this limitation if you rarely end up changing dependencies. For example, could you simply symlink the venv/ or node_modules/ in your new worktree? Let me know if you have any experience of doing this or something similar!

I'm convinced, let me use them!

 First of all, git worktree list or git worktree remove --force is far too much to type out each time. I have the following very basic set of aliases:

alias gw="git worktree"
alias gwl="git worktree list"
alias gwr="git worktree remove"

export PATH="$PATH:/home/username/scripts"

 However, the real trick is setting up your own script for git worktree create to suit your workflow.

 There are only two reasons I create a new worktree. Either I want to test and review a colleague's work, or I want to create my own worktree to mess around with. Either way, I would have to manually create the worktree, cd to it, run my personal justfile rules for instantiating the environment, and only then could I start on whatever I was doing. Around the fifth time I was auto-piloting my way through this sequence of typing, I decided to spend 10 minutes creating a script to do exactly this. It was a good idea. Now when I run gwc <name>, the script will pull the branch from the remote if it exists and set up a new worktree for it, or if it doesn't exist, it will create a new worktree for it. Finally, it will copy over my personal, not git tracked utilities, and initialize the project. I highly recommend customizing this to suit your needs!

#!/bin/bash

set -e

if [ $# -eq 0 ]; then
    echo "Usage: gwc <branch-name>"
    exit 1
fi

BRANCH_NAME="$1"
WORKTREE_PATH="../$BRANCH_NAME"

# If worktree already exists, just cd to it:
# NOTE: Can't cd from a script, so only option is to manually cd.
# Other option is to have this as an alias in zshrc to run
# `gwc ... && cd <new-dir>` or a wrapper function in general -_-
if [ -d "$WORKTREE_PATH" ]; then
    echo "Worktree already exists!"
    # cd "$WORKTREE_PATH"
    exit 0
fi

# Always fetch first to ensure we have latest remote refs
git fetch origin

# Check if branch exists on remote
if git show-ref --verify --quiet refs/remotes/origin/"$BRANCH_NAME"; then
    # Remote branch exists - ensure worktree points to remote head
    if git show-ref --verify --quiet refs/heads/"$BRANCH_NAME"; then
        # Local branch exists - delete it and recreate from remote to ensure sync
        git branch -D "$BRANCH_NAME" 2>/dev/null || true
    fi
    # Create tracking branch and worktree from remote
    git worktree add --track -b "$BRANCH_NAME" "$WORKTREE_PATH" origin/"$BRANCH_NAME"
elif git show-ref --verify --quiet refs/heads/"$BRANCH_NAME"; then
    # Only local branch exists - use it as is
    git worktree add "$WORKTREE_PATH" "$BRANCH_NAME"
else
    # Branch doesn't exist anywhere - create new branch
    git worktree add "$WORKTREE_PATH" -b "$BRANCH_NAME"
fi

# Copy files that live out of git tracking
cp .env "$WORKTREE_PATH/.env"
cp web/.env "$WORKTREE_PATH/web/.env"
cp justfile "$WORKTREE_PATH/justfile"

# Set strictPort to false so that worktrees can run concurrently
sed -i '' 's/strictPort: true,/strictPort: false,/g' "$WORKTREE_PATH/web/vite.config.ts"

# Install dependencies
cd "$WORKTREE_PATH/web"
bun install
# When working on backend stuff, do just init_project also

# Switch to the worktree directory
# NOTE: Can't cd from a script, so only option is to manually cd.
# Other option is to have this as an alias in zshrc to run
# `gwc ... && cd <new-dir>` or a wrapper function in general -_-
# cd "$WORKTREE_PATH"
~/scripts/gwc - There are a lot of comments here, but I promise you it's very straightforward!

Conclusion

  • I hope you either already are using worktrees, or I have managed to convince you to try them out!
  • Keep learning your tools! Maybe new features haven't been released since you first learned to use them, but maybe you can now understand different means of using the existing features.
Show Comments