Joost de Ruijter

back

Speeding up your development workflow with Git Worktrees

29-12-2021

Introduction

Consider the following scenario; you are working on a new feature. Then all of a sudden you get a notification:

  1. Something broke in production and you need to take a look at the problem
  2. You need to approve a PR
  3. A coworker is struggling with a problem and needs an extra set of eyes

Point being, you have to switch from working on your feature to doing some work on another branch. Now how can we store our "in progress" feature changes and switch to another branch? Well, it's easy. You just stash your changes, checkout the new branch:

$ git stash
$ git checkout some_other_branch

Stashing allows us to "save" the current state of the working directory, which in this case is our feature. Now we can do the work we need to do on the other branch. Once we are finished we can just apply the stash we previously saved:

$ git stash apply stash@{0}

And we are back to working on our feature! As you can see, if you just have to do this process once, it's not that bad. Unfortunately, in reality that's often not the case. As soon as you have to do this stashing ritual multiple times, it can get quite tedious and messy. What if you have a big list of different stashes you need to choose from? Or when you have a multiple stashes across multiple branches?

So.. how could we improve this? By using git worktrees!

What are Git worktrees?

Git worktrees allow you to have multiple commits checked out at the same time. Normally when you checkout a repository, you have just one folder containing all the source files. With git worktrees, you can create a folder for each commit that you want. To demonstrate this further, let's take a look at git worktrees in action.

Git worktrees in action

Let's first create a small sample Angular project. Our project will be a todo list with a few hardcoded items that the user can mark as done. This looks as follows:

Todo 1

I've added a few sample branches which we will use to demonstrate how git worktrees works in practice. You can find the project here.

Checking out the project

First, we'll have to clone the repository. Instead of doing a regular clone command, we will pass in an extra option: --bare. This makes sure that we checkout a "bare" repository.

$ git clone --bare https://github.com/joostderuijter/git-worktrees.git git-worktrees

Now if we cd into git-worktrees and list what's inside, we see the following:

Directory listing 1

All we see is just a bunch of stuff that normally resides inside of your .git folder. There's no worktree present. This is because we'll have to add these ourselves!

Let's create a worktree for the master branch. We can do this with the following command:

$ git worktree add master

This gives us the following output:

Preparing worktree (checking out 'master')

Git checks if the name of your worktree corresponds with any existing branch. Because we already have a master branch, it automatically checks out master instead of creating a new branch (we'll get back to this detail in a bit).

Now if we list the contents of our git-worktrees directory, we see the following:

Directory listing 2

We have a new folder! If we cd into the master folder, we see that we end up on the master branch. Inside we can find all of our source files:

Directory listing 3

Great! We're getting somewhere.

Adding a new feature

For the sake of this little demonstration, let's add a new feature to our todo list. We'll make the border around the todo list a bit thicker and paint it red.

Let's create a new branch from master called border-feature which we will use to do our work on. Instead of creating this branch the old way ( git checkout -b border-feature ), we'll do the same trick that we did above with the master branch. We'll cd back to our bare repo and create a new worktree.

$ cd ..
$ git worktree add border-feature

Upon running this, we get the following output:

Preparing worktree (new branch 'border-feature')

Git sees that we don't have a border-feature branch yet in our repository, and because of that it creates a new branch for us. Now if we list the directory contents of the repository, we see the following:

Directory listing 4

We can cd into this branch to do the work required for our new feature. By now you should start to see how worktrees make it a lot easier to switch between different branches / versions of the code.

Now let's implement our feature. First we'll add some thickness to the border:

.main {
    width: 200px;
    border: 6px solid black;
}

Done! Let's commit our work:

$ git add .
$ git commit -m "Added thickness to border"

Quickly switching between versions

Now let's start working on adding a red color. But before we manage to start on this feature, we get a message that somebody found a bug on production.. the entire background of the todo list is blue! This change was done while we were working on increasing the border thickness.. The boss tells us that we have to take a look at it. No problem! We'll just create a new worktree based on the branch that is currently deployed on production (for demonstration purposes, we'll use a branch called production for this).

$ cd ..
$ git worktree add production
$ cd production

Todo 2

Yeah.. that's not looking too good. Let's fix the bug and push the fix to the production branch:

$ git commit -m "Fixed blue background on production"
$ git push origin production

Todo 3

Much better!

Removing worktrees

Now that we are finished with our work on production, we can remove the worktree. For this we'll need to the following steps:

  1. Remove the production directory
  2. Prune the production worktree
  3. Remove the local production branch

We can do this with the following commands:

$ rm -rf path/to/worktree
$ git worktree prune
$ git branch -D production

The nice thing about the git worktree prune command, is that it automatically detects and removes information about all the worktrees that don't exist anymore. So we don't have to manually tell git to remove / prune each individual worktree.

If at some point we want to list all of the worktrees we have checked out in the repository, we can run the following command:

$ git worktree list

This gives us the following output:

Directory listing 5

Great! Now our hotfixing work is done, we can continue working on our feature by just going back into the border-feature directory and picking up where we left off (until the next time we get a message..).

Conclusion

These are some of the basic things you can achieve with git worktrees. I haven't been using the feature for that long, so I haven't done much more advanced stuff with it. One thing I really like about it, is that you can tweak it to your own liking. For example, let's say you spend a lot of time pushing out various hotfixes to production. In that case, you can always have a git worktree checked out called hotfix, which tracks the branch that runs on production. If the need arises to push out a hotfix, you'll always have a worktree ready to use.

It's a great hidden feature that I haven't heard many people about, so be sure to give it a shot!

References

  1. https://git-scm.com/docs/git-stash
  2. https://www.geeksforgeeks.org/bare-repositories-in-git/
  3. https://git-scm.com/docs/git-worktree