The magic of `git worktree` — How I multitask despite tests taking forever to run.
Tags: git, productivity
Published: 2022-12-13 02:45:57

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:

```sh
git worktree add /path/to/worktree branch
```

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:

```sh
git worktree list
```

Moved:

```sh
git worktree move /old /new
```

Or removed:

```sh
git worktree remove /path/to/worktree
```

## 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:

```sh
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}"
```

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](https://git-scm.com/docs/git-worktree).