Git worktree has become a major part of how I use Git over the past year. Anytime I mention it somewhere however, I get reactions from people who have never heard about the feature. Others have heard about it, but don't know how exactly it works or why it's beneficial. That's why I decided to write a short tutorial/introduction on this awesome feature that is baked right into the very git you are already using. I hope this can help people discover worktrees and be a gentle introduction on how to get started using them.
Git worktree facilitates working with multiple branches.
In a normal Git workflow, you can only ever have one branch checked out at a single time.
If you want to switch to another branch, you use git checkout
or git switch
.
This works most of the time, but what if you have changes in your worktree.
Switching to another branch would update your worktree to be in sync with the one you checked out, thus overwriting your local changes.
The solution in this case would be to stash your changes using git stash
before switching.
Using stashing to switch branches works fine, but it isn't the nicest approach, especially when working on multiple branches at the same time. This is where Git worktree can help.
Git worktree is a command in Git that allows the user to create multiple worktrees in the same local repository. It has some options to do this, which we'll go over a bit later. You could think of git worktree as having multiple git repositories inside the same parent repository.
While most things work exactly the same way as they normally do while using Git, there is one big difference. A repository that uses Git worktree has to be a bare repository. This simply means that by default, there will not be any worktree (the actual files in the repository). We have to 'attach' the branches we want to different directories, which will be their own worktrees. What this means in practice is that a cloned repository will look like this. Notice how there is no actual files, which would be the case for any repository we clone.
The question is how to get to the worktree workflow from the normal one.
To start off with, we need to clone the repository we want to work in with git clone --bare <url>
.
This will create a repository that looks like the following.
Notice the '.git' added to the directory.
This is a convention that is used for bare git repositories, and I would recommend to keep it there.
However there is a first problem.
The remote branches aren't visible when we run git branch -vva
.
To solve this, we need to tell Git to fetch all the refs from the remote.
Open the config
file in the repository, and add the following line to the remote we want to fetch all refs for.
fetch = +refs/heads/*:refs/remotes/fork/*
When we run git fetch
now, we'll see the remote refs as well.
Now adding the local master branch to its own worktree is as simple as git worktree add master
.
I also like to set the upstream branch so git push
and git pull
work without specifying the branch.
This can be done just like we would normally do with git branch -u <remote-branch> <local-branch>
.
In this case, running git branch -u fork/master master
will set the remote master branch as the upstream branch for the local master branch.
If we run git branch -vva
again, we'll see the following.
We can now see that the local master branch has its upstream branch set by looking at the blue text next to it. For some reason, adding the main branch as a worktree doesn't show its worktree location, so here I quickly added another branch just to show that off as well.
That is basically all there is to it. Now we can cd
into the directories and use any normal Git commands.
Switching between branches is now as simple as changing directories.
If we want to remove a worktree because we no longer need it, we can do so with git worktree remove <worktree>
.
If there are files in the worktree that are tracked by Git, modified, but not yet commited, this command will fail.
To remove the branch with modified files, we would have to add the --force
flag to the command.
- Most IDEs don't offer special support for worktrees. They just treat each worktree as a separate project. You can always soft link files between them. I like to put folders like
.idea
and.vscode
in the bare repo, and soft link to them from the worktrees. - While it seems possible, unfortunately it's not possible to start a repository as a bare repository, as its HEAD will point to a non-existant ref. It's very hard to solve this. That's why it's better to start with a normal repository, upload it to a remote, and clone it as a bare repository.
- I have some extra options in my
.gitconfig
that make working with worktrees simpler. Here is the file for people who want to copy some of the options and aliases.
[init]
defaultbranch = main
[pull]
rebase = true
[status]
short = true
branch = true
[commit]
gpgsign = true
[fetch]
prune = true
[worktree]
guessRemote = true
[core]
editor = nvim
[alias]
# Show a list of all the files that are being tracked.
show-tracked = ls-tree --name-only -r HEAD
# Stage the file for the next commit, but don't start tracking new files.
stage = add -u
# Make git aware of the existence of the file, but don't automatically stage
# it.
track = add -N
# Unstage the file from the next commit.
# Will also untrack a file that hasn't been commited once, so watch out!
unstage = reset --
# Make git unaware of the existence of the file (automatically unstages,
# since git doesn't know the file anymore)
untrack = rm --cached
# Shortcuts
l = log --oneline --decorate=full -n 40
c = commit
b = branch -vva
r = remote -v
s = status
sl = status --long
st = stage
ust = unstage
t = track
ut = untrack
a = add
dt = difftool
f = fetch --all
- For Rust users, it can be a good idea to put
.cargo/config.toml
inside the bare repository, and add the following to it. This will cause all target files to be put in the same directory, speeding up compile times significantly, and making cleaning simpler.
[build]
target-dir="target"
I'll list some of the sources I used last year to learn how exactly worktrees worked, as well as some tutorials that might explain certain parts in more detail/differently.
- GitKraken's excellent tutorial on Git worktree
- ThePrimeagen's video explaining Git worktree
- Git's included man page
man git-worktree
It's my first time writing a gist. If contributions are possible and you notice an error or somthing that can be improved or needs more detail, feel free to create a pull request. :)
This is nice, but instead of cluttering the project directory with all of the files related to the bare clone, you could create a
.bare
directory inside of the project directory and then point git to it via a.git
file. Then you've got a nice directory structure like so:This structure was introduced to me by @slightlyoff (via his blog): https://infrequently.org/2021/07/worktrees-step-by-step/