Git Worktrees

Git Worktrees

git's best hidden old feature that you never knew about !

ยท

8 min read

Hello fellow developer ๐Ÿ‘‹

Have you ever been in one of the following situations ?

  • You are developing a feature and your PM just had his dose of 'cool ideas' at lunch and really wants you to drop everything and work on his new brain child ?
  • Your junior cannot, for his paycheck's sake, discover how to make something work and after a 2 hours call you just ask him to push his code so you can quickly take over ?
  • You push to production, treat yourself a cake for a job well done, and start working on something else when the CI/CD starts screaming alerts on 3 different devices and you need to drop everything and put out a fire ?

For times like this, most people either do git stash or a git commit -m "wip" in order to save what they were doing so they can tend to other priorities.

Quickly changing your` mindset to deal with another problem requires you to re-align your focus and get your bearings, now, depending on many factors changing branches may involve setting up a whole dev environment, like running migrations or installing dependencies, and that takes time.

I was quite happy with following either one of the above mentioned methods during the entirity of my short career. That is, until I started working in a project with a huge number of developers, which translates in having to 'refresh' alot of stuff everytime you switch branches. Luckily, recently I came across a git feature that seems to have been introduced way back in 2015, with the release of Git 2.5.

What are Git Worktrees ?

Git Worktrees allow you to quickly switch between branches without having to re-initialize your whole dev environment. When you do git worktree add you create what is called a "linked worktree" . This is the worktree equivalent of doing a git checkout -b as it creates both the worktree and the branch.

~/dev/kuro master
โฏ git worktree add ../.worktrees/kuro-fix-css -b fix-css
Preparing worktree (new branch 'fix-css')
HEAD is now at 9652578 kuro@8.0.4

~/dev/kuro master
โฏ cd ../.worktrees/kuro-fix-css 

~/dev/.worktrees/kuro-fix-css fix-css
โฏ git status
On branch fix-css
nothing to commit, working tree clean

You can read the documentation for more details, but let me breakdown what I did.

  • I was in my master branch with the latest changes
  • I added a linked working tree called fix-css which is coming from one directory up with in the .worktrees folder. The -b flag creates and checks out a new branch starting at the HEAD when I run the command.
    • Opposed to linked working trees we have the main working tree which is the main repository directory.
  • I switch to my worktree directory.
  • I'm now working on a newly created branch from master called fix-css

I can then commit to this branch, but if for some reason I need to create another worktree from this my master branch I can just do this:

~/dev/.worktrees/kuro-fix-css fix-css*
โฏ git commit -m "some changes"
[fix-css d04039b] some changes
 1 file changed, 12 insertions(+), 12 deletions(-)

~/dev/.worktrees/kuro-fix-css fix-css
โฏ git worktree add ../kuro-from-master master -b new-branch
Preparing worktree (new branch 'new-branch')
HEAD is now at 9652578 kuro@8.0.4

~/dev/.worktrees/kuro-fix-css fix-css
โฏ cd ../kuro-from-master

Breaking down the worktree add command, we're adding a worktree in ../kuro-from-master pointing at our master branch, and again the -b flag creates and checks out the new branch in the worktree. At this point, if we change directory to any of the worktrees, or the original worktree (called main working tree) and we run git branch --list we can see that we have 1 branch per worktree and all our regular branches.

Behind the Scenes

As I'm sure you have realized by now, we are basically creating a full copy of everything that's not targeted by our .gitignore rules. Admitedly this is not ideal if you are working in a VM or in a more limited machine. The codebase may not be that big, but remember you will most likely need to install dependencies everytime you initialize a worktree, unless you don't have your depencies listed in .gitignore, and if you do, please stop and take a moment to think about what you're doing with your life.

There are two reasons we don't track our dependency folders with git

  1. They can get pretty big, pretty fast.
  2. Everytime you update a dependency, you commit the compiled dependency changes.

That's why we have those fancy install scripts.

So for each worktree we're maintaining we have to allocate disk space for the project and all the dependencies. For a simple Javascript project, this goes to 400-600MB per worktree, so we should be mindful of deleting old worktrees with the git worktree remove <worktree_path> command.

Pros & Cons

Let's quickly go over some pros & cons of the vanilla worktree feature that comes out of the box when you install git compared to the workflow we all know and love

Cons ๐Ÿ‘Ž

  • You need to install all dependencies everytime you initialize a worktree
    • With stash & checkout you already have a dependency folder and only install the necessary updates.
  • If not controlled, can take up alot of disk space.
    • With stash & checkout you only have on working directory.
  • Unable to create/checkout branches checked out in worktrees
    • If you have a worktree pointing to a branch, you will not be able to checkout that branch in the main working tree
  • Easier to write checkout <branch> than cd <path_to_worktree>
    • Yes. Read the following section where I introduce some tools that deal with this.

Pros ๐Ÿ‘

  • You ONLY need to install dependencies whern you intialize a worktree.
    • Unless you're constantly pulling changes from other branches, you only need to install dependencies once.
  • You only need to run your environment scripts once.
    • If you work with databases, you need to reset or run migrations/backfills everytime you switch branches.
  • Worktrees allow you to just save your file and move contexts.
    • You avoid WIP commits & endless stashes in your history
  • You can run two instances of your code editor in different branches.
    • You can easily cd into your worktree directories and open your code editor.
    • To my knowledge, there's no way to do this with only the main working tree

From this list, you can see that the main downside to have in consideration is to mind how many worktrees exist at the same time in your machine because of the space they can take.

Tools & Suggested Workflow

My main issue with worktrees was always how different the command structure was from the all the git checkout I was used to doing. If I was using it to create worktrees from the CLI, my first move would be to create two functions to ensure I had one centralized .worktrees directory and that I could easily change into that directory.

gwt () {
  branch=${1}
  git worktree add ~/dev/.worktrees/$branch -b $branch
}
gwtc () {
  branch=${1}
  cd ~/dev/.worktrees/$branch
}

If you're looking for simplicity and you don't have a big number of projects in your machine, this script will suffice for your CLI needs, however, I have been using two tools that make this process way more straightfoward while dealing with multiple projects.

GitLens

GitLens is a well known VSCode extension and the reason I stumbled across this feature. They have recently released their take on bringing worktrees to VSCode and it makes creating worktrees and checking out respective branches really easy. You can select where you want the worktree stored (by default it uses the ../<project_name>.worktrees directory) and allows you to easily Open VSCode on and from any of the repo's worktrees.

Git Worktree Switcher โšก

For the terminal, another great tool I've found is a little bit less main stream, is Git worktree switcher! โšก . This little script can be installed quite easily and provides a nice way to switch between worktrees with fuzzy find support. In the example above, I can just do wt fix-css and the script takes me to the fix-css worktree directory, and I can also jump back to the main working tree with wt - . This also renders my second function completely useless.

Workflow

Regarding my workflow, I have the rule of only creating worktrees for either branches that are ready but are waiting for something (like a PR review or another feature being merged), to lend a hand to a colleague in another branch, or if I'm in the middle of something and a new bug required my immediate attention. Remember that for each worktree you create, you will take up disk space for the dependencies. When the need for a worktree arises I usually do the following:

  • I checkout the latest commit from the main branch
  • In VS Code, I use GitLens to create the worktree (and the branch if it doesn't exist already, from the Source Control sidepanel

image.png

  • I open the worktree with VSCode
    • GitLens allows you to open the worktree while keeping your main working tree window.
  • On the CLI I do wt <branch_name> and install the project dependencies.
    • In this workflow, the branch name and the worktree directory will be the same.
  • If I need to switch back to the main worktree I can do wt - on the comand line and open the code editor through GitLens (or by doing code . on the CLI)
  • Once I'm done and push my changes, I usually leave the worktree until the branch is merged.
  • When I no longer need the worktree, I can remove it in VSCode on the Source Control sidepanel.

And this is pretty much it. By doing this, I have saved alot of time by not having to re-install depdencies or run migrations everytime I switch branches

Conclusion

Hope you found this article interesting. I have been using worktrees for a very short time, so I bet there are way more integrations and tools for this amazing and hidden git feature. If you're aware of any other tools and hacks realted to worktrees, please leave a comment.

Check out my website to learn about my most recent projects and follow me on Twitter ๐Ÿค™

Did you find this article valuable?

Support David Morais by becoming a sponsor. Any amount is appreciated!