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
young padawanjunior 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.
`bash
~/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 theHEAD
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
calledfix-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:
`bash
~/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
- They can get pretty big, pretty fast.
- 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.
- With
- If not controlled, can take up alot of disk space.
- With
stash
&checkout
you only have on working directory.
- With
- 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
- If you have a worktree pointing to a branch, you will not be able to
- Easier to write
checkout <branch>
thancd <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 avoid
- 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
- You can easily
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.
bash
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
- 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 doingcode .
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 🤙
Top comments (0)