8 min read

Get your git game up with git worktrees

Git worktrees helps with working on multiple things at once in git. It is a better alternative to "git stash". Learn how to use worktrees in your daily git workflow.
Get your git game up with git worktrees

Git has been in the software industry for more than a decade now with a lot of features & commands and whilst most of the time simply knowing concepts like how to merge a branch, and how to rebase are good enough for everyday git workflow, there is something new to learn every day with git.

Once such feature is git worktrees. In this tutorial, we will learn about why & how you can get started with git-worktree, and by the end of this article, you will be able to change the way you work with git.

Here is a brief overview of what we will be covering in this article:

What are worktrees anyway?

Worktrees let you check out multiple branches of a repository in different directories simultaneously. Unlike cloning a repo in a separate directory, worktrees share the same git history (a.k.a. contents of the .git directory), which makes them faster and uses less space (which is where all the magic happens).
Think of worktree as creating replicas of your main repository at various stages of history. Worktrees live on your system, i.e., you can't push them to a remote origin like in the case of branches & tags.

worktrees-representation

What are the use cases?

You had been asking what would I use git worktree for, I am happy with my current flow. So prepare to be your mind blown. Here are the top 5 reasons you might want to start using git worktrees:

  1. When you have to work on an immediate fix
    When you are working professionally, dealing with bugs immediately becomes a top priority, but this can sometimes hinder your ongoing work. In an ideal workflow, you would git stash your current changes to work on the fix and come back later to resume the work, but managing stashes can become quite tedious with time especially if your work requires frequent context switching. In those cases having individual folders to work on different items can be a productivity helper.
  2. Explore different approaches to fix or build something
    A lot of times we as developers are experimenting every day, and this requires us to try out different strategies to solve a particular problem. Consider a scenario where you are trying to introduce tests in your codebase. Now you have to choose from 2 different testing frameworks, to do that you have to eventually understand how to use each framework in this scenario you can create 2 different worktrees to compare both the testing libraries.
  3. Code reviews
    As a senior developer, you are always getting requested for a code review from your teammates. Reviewing pull requests timely is also an important activity when working in a team. Often, you will find yourself in situations where you have to execute the code in review to understand it correctly. Again in this case this can hinder your current work, create a different worktree just for reviewing the code and then remove it once the PR is merged.
  4. Reduce disk space usage for large repositories
    Since git worktrees share the same git history, this helps optimize working with big repositories. A typical example would be when you have large-sized assets in your repo.
  5. Experiments are needed for both personal solo projects as well as team projects
    We talked about why worktrees are a good bet when you are working with teammates, but even if you are a freelancer or work solo, embedding worktrees in your daily flow can be a boon as it gives you the ability to experiment with multiple ideas at the same time.

How to use worktrees?

Let's set up a demo git repo to learn worktrees shall we?

$ mkdir mcu && cd mcu
$ git init
$ echo "worktree demo" >> README.md
$ git commit -am "init README"

To start, we use the git worktree list command to check whether we have any worktrees at all. To our surprise, our repository has a default "main" worktree that always exists. This worktree is called the "main worktree" and is always checked out with the main git branch.

$ git worktree list
/home/bhupesh/mcu                       5837763 [main]

Worktree information is shown in the following form

/path/to/worktree    <commit> [branch]

Now, to create a worktree, we use the git worktree add </worktree/path> command.

$ ls
README.md
$ cat README.md
worktree demo
$ git worktree add ~/mcu-worktrees/thanos
Preparing worktree (new branch 'thanos')
HEAD is now at 5837763 ✨ new file

git worktree add <path> will automatically create a branch whose name is the last component of the <path>, in our case a new worktree will be created from a branch named thanos. You can also verify this by listing all your branches with git branch -a.

You can also refer to any pre-existing branches while creating worktrees by using the add command in the following form:

git worktree add <path> <branch>

If you will list worktrees now there will be a new entry:

$ git worktree list
/home/bhupesh/mcu                       5837763 [main]
/home/bhupesh/mcu-worktrees/thanos      5837763 [thanos]

Worktrees created using git worktree add are called linked worktrees. We will be using this reference for the entirety of this tutorial.

Change your current working directory to ~/mcu-worktrees/thanos. You will find that the repository contents are exactly the same as your main repository. It feels like we cloned the whole repository again to a new directory but with a different name. All git commands work exactly the same as they would in the "main worktree".

Let's do some work in our thanos worktree

$ echo "work in worktree" >> README.md
$ git add .
$ git commit -am "new commit from worktree"
[thanos d555e02] new commit from worktree
 1 file changed, 1 insertion(+)

Once your work is done in a worktree, ideally you will end up merging the thanos branch with the main branch. To do that switch back to your main worktree.

$ cd ~/mcu
$ git merge thanos
Updating 5837763..d555e02
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)
$ cat README.md
worktree demo
work in worktree

You can now choose to remove it by using the git worktree remove command.

$ git worktree list
/home/bhupesh/mcu                       5837763 [main]
/home/bhupesh/mcu-worktrees/thanos      5837763 [thanos]
$ git worktree remove /home/bhupesh/mcu-worktrees/thanos
$ git worktree list
/home/bhupesh/mcu                       d555e02 [main]

Note that the branch is not removed only the worktree directory is removed. You will still be able to access the thanos branch.

Where to store worktrees?

Ideally, the best location to store worktrees is outside the "main" directory. In our case, we use the $HOME directory as the parent for both the main & linked worktree directory. This is what our directory structure looks like:

$HOME
│
├────mcu (main git directory or main worktree)
│    │
│    ├────.git
│    │
│    └────README.md
│
└────mcu-worktrees
     │
     └────thanos (linked worktree)
          │
          ├────.git
          │
          └────README.md

Another strategy to set up a git worktree flow could be where you put all your worktrees in a .worktrees folder in your main repository & then git ignore it by adding it to the .gitignore file. There is no best practice for this and you are free to choose from any of the two different strategies.

How does a worktree look under the hood?

A linked worktree directory has the following contents:

some-worktree
├── .git
├── ...
└── README.md

If you had noticed, a linked worktree contains the .git file (instead of .git as a directory in case of the main worktree). The .git file stores the location of worktree information inside the main worktree.

gitdir: /home/bhupesh/mcu/.git/worktrees/thanos

Quoting the git manual,

.git/worktrees directory contains administrative data for linked working trees. Each subdirectory contains the working tree-related part of a linked worktree.

.git/worktrees/
└── thanos
    ├── commondir
    ├── gitdir
    ├── HEAD
    ├── index
    ├── logs
    │   └── HEAD
    └── ORIG_HEAD

Let's have a look at what some of these files represent:

  1. commondir is a file is storing the path to the .git directory of the main worktree (e.g., /path/main/.git).
  2. gitdir is a text file containing the absolute path back to the .git file that points to here. Used to check if the linked repository has been manually removed and there is no need to keep this directory any more (it becomes prunable).
  3. HEAD is a reference to the latest or recent commit.

Improving the git worktree CLI experience with fzf

Till now you may have noticed that worktrees are great but they have a constant overhead of "switching your current working directory" frequently, especially if you have a lot of them.
We can fix this by writing a small shell script powered by a fuzzy search tool like fzf

#!/usr/bin/env bash

# FZF Wrapper over git to interactively switch git worktrees

worktree=$(
    git worktree list | fzf \
        --prompt="Switch Worktree: " \
        --height 40% --reverse |
        awk '{print $1}'
)

cd "$worktree" || return

Now to execute this script use the command source script.sh. Here is a small demo of quickly switching between multiple worktrees in a matter of seconds.

worktree-switch-demo

Worktree gotchas!

Now, once we have cleared worktree basics, there are some gotchas that you should be aware of to have a seamless experience while using them.

  1. When you are using worktrees, checking out branches that are already checked out in some worktree is not possible. Git will inform you when you try to do that.

    fatal: 'main' is already checked out at '/home/bhupesh/mcu'
    
  2. Git will warn you when you try to delete a branch using git branch -D which is already checked out in some worktree.

    error: Cannot delete branch 'thanos' checked out at '/home/bhupesh/beep
    bop/mcu-worktree/thanos'
    

    So to delete the branch, you will first have to remove the associated worktree by using the git worktree remove command as discussed above.

  3. If you remove a worktree manually, say using the rm -r command or via your file manager, the git worktree list command will indicate that the worktree is prunable, since the associated directory doesn't exist anymore.

    /home/bhupesh/mcu                        5837763 [main]                                             
    /home/bhupesh/mcu-worktrees/better-idea  5837763 [better-idea] prunable
    

    Git's Garbage Collector will automatically clear this worktree, although you can do this yourself by running the git worktree prune command.

  4. Git will warn you while removing a worktree if that worktree has some uncommitted changes.

    fatal: '/home/bhupesh/mcu-worktrees/better-idea' contains modified or untracked files, use --force to delete it
    
  5. Moving linked & main worktrees is possible when you want to reorganize your work directories. The issue with this case is that every time you move a worktree manually, the main worktree will not be able to access it. To fix this you can use the git worktree repair command which reestablishes the lost connections between the main & linked worktree.
    To prevent this lost connection problem altogether, use the git worktree move command when reorganizing your worktrees.

  6. Support for git submodules is experimental and sort of incomplete. So if your workflow relies heavily on submodules, you should better rely on git stashes instead of worktrees.

  7. Project-specific dependencies like JavaScript node_modules and Python venvs will have to be reinstalled or re-configured for a linked worktree since they are usually git ignored, so git will not add them to the new worktree index.

Tools & editor integrations

If you prefer an in-editor workflow for using worktrees, you can use VSCode plugin "GitLens" which has a pretty good worktrees support. Although at the moment, it's only available on public repositories or for paid accounts.

Conclusion

Worktrees let you work on multiple things at once, it's a better alternative to git stash and gives you flexibility on having multiple & identical "git environment".