DEV Community

Austin S. Hemmelgarn
Austin S. Hemmelgarn

Posted on

The magic of `git worktree` — How I multitask despite tests taking forever to run.

As a programmer, sometimes there are situations where you need to jump to a different task when you’re right in the middle of something. Most decent VCS software provides a selection of ways to approach this, ranging from things like patch queues, to branches, to any number of other mechanisms.

However, one of the big limitations of these mechanisms is that they require you to change out the entire working tree, so you can’t use them if your existing task happens to require the working tree, such as running a build or tests, or if you need to preserve the state of an unclean working tree.

You can kind of work around this by creating another clone of the repository, but this has a couple of major limitations:

  • Special effort needs to be made if you need to keep the two working trees in sync.
  • Special effort needs to be made to keep track of exactly which copy of the repo you’re working with.
  • Twice as much disk space is required.

With git, you can avoid most of the extra disk space by using git clone --shared to clone the local repository, but this is still a wholly separate repo, so it still runs into the other two issues.

Thankfully git has a wonderful solution to this...

Enter git worktree

One of the lesser known features of git is that it supports having multiple working trees attached to the same repository. Each git repository (except for bare repositories) has a ‘main’ working tree, which is the one that actually has the .git directory for the repository, plus zero or more ‘linked’ working trees, which use a file called .git to link back to the .git directory.

To manage these linked working trees, git provides the git worktree subcommand. Creating a new linked working tree that has a specific branch checked out is simple as running:

git worktree add /path/to/worktree branch
Enter fullscreen mode Exit fullscreen mode

Instead of a branch, you can specify any tag, commit, or other ref to check out that specific ref. If you don’t specify a ref to checkout, then a new branch will be created automatically from the currently checked-out commit with a name based on the basename of the new worktree.

Once created, you can change to a linked working tree, and then use it (mostly) just like a regular git repository. The key difference is that it shares a .git directory with the main working tree. This means that if you create a new branch in the linked working tree and add commits to it, that same branch and commits will be immediately accessible in the main working tree and any other linked working trees.

Worktrees can be listed:

git worktree list
Enter fullscreen mode Exit fullscreen mode

Moved:

git worktree move /old /new
Enter fullscreen mode Exit fullscreen mode

Or removed:

git worktree remove /path/to/worktree
Enter fullscreen mode Exit fullscreen mode

But how to use it?

Say as an example that you’ve got a build running in your main copy of the repository, but need to hop to a different branch to work on something else in a different branch while waiting for the build to finish. You can easily do this using git worktree from a different shell as follows:

WORKTREE_PATH="/path/to/tree"
git worktree add "${WORKTREE_PATH}" branch
pushd "${WORKTREE_PATH}"
# make needed changes and commit them...
popd
git worktree remove "${WORKTREE_PATH}"
Enter fullscreen mode Exit fullscreen mode

The same approach can be used to create temporary work trees for running tests (I do this on a regular basis, especially when dealing with cross-architecture builds where things need to run under QEMU userspace emulation for testing), or as a way to provide two full copies of different versions of a source tree to compare using some tool that doesn’t integrate with git.

OK, what’s the catch?

As amazingly useful as git worktree is, there are still a few major limitations:

  • The .git file in a linked worktree embeds the path of the main worktree. Because of this, if you move the main worktree, any linked worktrees will become unusable until you run git worktree repair.
  • Similarly, the main worktree’s .git directory embeds the paths to any linked worktrees. This means that if you want to move a linked worktree, you either need to use git worktree move, or you need to run git worktree repair in the linked worktree after you move it.
  • If for some reason both the main and the linked worktrees get moved at the same time, you have to run git worktree repair in the main worktree, and pass it the new paths to each linked worktree.
  • While git technically supports per-worktree configuration, it’s tricky to use, and there are a number of gotchas.
  • Even if you have multiple worktrees, you cannot safely check out the same branch more than once.
  • Support for submodules is a bit lacking. It mostly works, but you have to force removal when removing a worktree that contains checked-out submodules, and have to manually move worktrees that contain submodules.

More details can be found in the git documentation.

Top comments (0)