Switching Branches With Git Worktrees

Switching Branches With Git Worktrees

Software developers are oftentimes subject to “context switching”. In essence, this means that they need to switch to some other task before they are able to finish the current one. This, of course, can have a multitude of reasons. Common ones include:

Git Worktrees

Thankfully, almost everyone nowadays is using some form of version control system like git, which allows to switch contexts (or, in this case, “branches”) without throwing away our work altogether. Most developers would do one of two things in a situation like this:

In this article, we will show you a third option of switching branches that doesn’t involve neither stashing nor committing. Let’s face it, the easiest and most convenient solution would be if we could just check out multiple commits at the same time without them interfering with one another, right?

That’s exactly what Git Worktrees allow us to do! Before delve into how it works in detail, let us first look at what a “git repo” actually consists of.

What Happens When We Clone a Repository?

Whenever we clone a repository the “standard” way via

git clone <repo-url>

We actually end up with two things. The first and most obvious one is often called the repository but, actually this is our working tree. It includes all the files and directories we have put under version control. It is set to track our main/master branch by default.

cd <repo-name>

C:/working/directory/repo-name (main -> origin)
ls
-> ... README.md src/ ...

If we look at everything git generated, we will also find a .git directory.

ls -a
-> ./ ../ ... .git/ .gitignore README.md src ...

This is what actually comprises our repository. It includes all the necessary files and configuration to make version control work and will record all the changes we make to our code base. Now, if we type:

git worktree list

It should output something along the lines of:

C:/working/directory/repo-name <commit-ish> [main]

As we can see, by cloning the repository, git has already created a worktree for the branch main for us. Let’s next look at how we can add more worktrees.

Quick, We Need a Hotfix!

Imagine we are working on a new footer component for our fancy web-app

C:/working/directory/repo-name/src/components (feature/add-footer-component -> origin)
ls
-> header-component.ts main-form-component.ts

C:/working/directory/repo-name/src/components (feature/add-footer-component -> origin)
vim footer-component.ts
# ... hack hack hack ...
# ... miraculously exit vim ...

C:/working/directory/repo-name/src/components (feature/add-footer-component -> origin)
ls
-> header-component.ts main-form-component.ts footer.component.ts

Suddenly, we get a call from our boss telling us that an important hotfix is needed for production. Someone made a typo in the header component that managed to slip through all stages of QA. Instead of the usual “Oof, this fix will take at least 2 weeks” we confidently respond: “No big deal, boss” and type:

C:/working/directory/repo-name/src/components
git worktree add -b emergency-typo-fix ../../../temp main
cd ../../../temp

What we did there was not magic but checking out a new (-b) branch called emergency-typo-fix from main, putting it in the /temp directory (one level up from the root directory!) and switching to that directory.

Since we checked out the new branch from the current main our unfinished footer component will not be in our new working tree, but it will still exist in our other working tree.

C:/working/directory/temp
ls
-> header-component.ts main-form-component.ts
vim header-component.ts
# fix typo -> save -> commit -> set upstream and push

cd -
C:/working/directory/repo-name/src/components (feature/add-footer-component -> origin)
ls
-> header-component.ts main-form-component.ts footer.component.ts

To instead checkout an existing remote branch locally, which will automatically track it, use:

git worktree add ../feature-directory featureX
-> Preparing worktree (new branch 'featureX')
-> Branch 'featureX' set up to track remote branch 'featureX' from 'origin'.
-> HEAD is now at <commit-ish> <commit-message>

For tracking information about our branches, we can always use:

git branch -vv
# add -a for listing remote branches too
# for even more info, use: git remote show origin

And if you were wondering: yes, we can still checkout other branches with git checkout from a worktree, under the condition that they are not already checked out in another worktree.

What Happens When We Delete a Worktree Directory Manually?

Now, we have saved the day with our hotfix but are left with a worktree and a branch that we no longer need. To get rid of the worktree type:

git worktree remove /path/to/worktree

This will remove the directory as well as the information of the worktree. If we manually delete the directory of the worktree, the reference to the worktree in .git/worktrees would still exist, and it would continue to appear in git worktree list. If this ever happens, just type git worktree prune, which removes stale references to worktrees that no longer exist.

Removing a worktree doesn’t remove the branch it tracked, so to get rid of it as well, we still need to execute

git branch -D <branch>

If we want to avoid forgetting this, we could, combine both commands

git worktree remove /path/to/worktree && git branch -D <branch>

How To Fix Issues When We Manually Delete Or Move Worktrees?

We have already covered what happens when we delete a worktree directory manually and how the prune keyword will help us remove the stale reference, but what happens when we move or rename a worktree directory?

For example:

git worktree add -b urgent-fix ../urgent-fox main

We accidentally spelled ‘fox’ instead of ‘fix’ for the directory, so we quickly rename it:

mv ../urgent-fox ../urgent-fix
cd ../urgent-fix

But upon typing git worktree list we still see

C:/working/directory/urgent-fox  123xyz [urgent-fix]

That is because our gitdir did not update itself when renaming the directory. Instead, we should have used

git worktree move ../urgent-fox ../urgent-fix

but it’s too late now, so how can we “repair” this damage? Simple. From the worktree that is out of sync, type:

git worktree repair

and we should see something like

repair: gitdir incorrect: C:/working/directory/main-worktree/.git/worktrees/urgent-fox/gitdir

The gitdir for this worktree that resides in our main worktree’s .git directory was updated with the new location of the worktree. Now git worktree list should list our repaired worktree location.

If instead we have moved or renamed our main worktree though, this means all other worktrees will not work anymore, since every reference will be wrong. We will get fatal: not a git repository errors. Again git worktree repair (run from our main worktree) fixes this issue and re-links all worktrees.

In a Nutshell

We have learned that we can create worktrees for as many branches (or commits) as we want. Each will be placed in its own directory, leaving our current changes untouched and allowing us to switch between branches without any interference as many times as we like.

We should now also know the differences between a git repository and a worktree as well as how to create and remove worktrees. In addition, we covered how to fix some of the more common issues that occur when we manually delete or move worktrees instead of using the appropriate commands for them.

What you may have already noticed during this showcase is that the folder structure of worktrees can seem a bit weird. We ended up with something like this.

- hotfix
	- ... source files ...
	- .git (file)
- awesome-web-app-repo
	- ... source files ...
	- .git/ (dir)
		- worktrees/
			- hotfix/
			- ...
		- ...
- some-other-repo

Instead, most people would probably want something more “clean” like:

- awesome-web-app-repo
	- .git/
		- worktrees/
			- main/
			- hotfix/
			- ...
	- main
		- ... source files ...
		- .git
	- hotfix
		- ... source files ...
		- .git
	- ...
- some-other-repo

For this result, we advise you to have a look at bare repositories which are awesome in their own right but can be a bit of a hassle to set up for remote tracking.

Read more Insights about Software Product Engineering.

Newsletter Subscription