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
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
Moved:
git worktree move /old /new
Or removed:
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:
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 rungit 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 usegit worktree move
, or you need to rungit 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)