Stash is not that great
Worktrees can prevent one of the most annoying problems of using git. Stashing.
But wait... did we not just learn stashing? Yes. So is it bad? No. ... I am confused
Worktree
You are working on feature branch foo_bar
. You are making great progress and
you are in flow state.
Just then, slack pings, lo and behold an emergency investigation is needed on
main
.
- You can
git add .
any non tracked files to the index (staging area) and then stash this change then change branches to do the investigation. - You can commit this change and change branches
- Use Worktrees
What is a worktree?
Generally when we say worktree
we mean a linked working tree
When you git init
a repo it creats the main working tree
. A linked working trees
is a just another tree much like main working tree just without
all the git history within the .git
directory. In fact, the .git
directory
is not a directory at all, but just a file pointing to the main working tree
directory.
Worktree Operations
Add
➜ git worktree add <path>
The basename
of the path
will be used as the branch name.
List
➜ git worktree list
Delete
There are a couple ways to go about this
- you can use
git
andgit worktree remove ../foo-bar
- you can use
rm -rf
andgit worktree prune
Benefits
they are cheap to make since they don't need .git
history. They are just a
pointer. They are just slightly more expensive than a branch. But you get a
commit to work on that is outside your main working tree. That means if you
need to pivot quickly you don't have to commit, stash, or anything, just create
a new linked working tree!
Cons
if each branch has a high setup cost. like if your npm install takes 100 minutes due to everything-js dependency, worktrees can be more of a pain.
Problem
create a worktree of hello-git
with the path name ending in foo-bar
.
Suggestion, ../foo-bar
when completed check out .git
found in the linked working tree. What's
different about this .git vs a main working tree .git?
Solution
➜ hello-git git:(trunk) git worktree add ../foo-bar
Preparing worktree (new branch 'foo-bar')
HEAD is now at 7488b35 Revert "E"
you will notice that when you change into ../foo-bar
that the branch name is
the path's basename
➜ hello-git git:(trunk) cd ../foo-bar
➜ foo-bar git:(foo-bar)
Checking out .git
➜ foo-bar git:(foo-bar) ls -la
total 24
drwxrwxr-x 2 ThePrimeagen ThePrimeagen 4096 Feb 29 19:00 .
drwxrwxr-x 128 ThePrimeagen ThePrimeagen 4096 Feb 29 19:00 ..
-rw-rw-r-- 1 ThePrimeagen ThePrimeagen 59 Feb 29 19:00 bar.md
-rw-rw-r-- 1 ThePrimeagen ThePrimeagen 65 Feb 29 19:00 .git
-rw-rw-r-- 1 ThePrimeagen ThePrimeagen 41 Feb 29 19:00 README.md
-rw-rw-r-- 1 ThePrimeagen ThePrimeagen 24 Feb 29 19:00 upstream.md
.git is a file! what!
➜ foo-bar git:(foo-bar) cat .git
gitdir: /home/ThePrimeagen/personal/hello-git/.git/worktrees/foo-bar
.git file shows that it is just a pointer to the main working tree. That is how it knows how to find all of the information and why you can switch branches in a linked working tree. Your edits are making edits to the main working tree's .git state
In other words, working trees are "light weight clones"
This means you NEVER have to stash again, if you don't want to.
You can also check out an existing branch with git worktree add <path> <branch_name>
Problem
Go back to hello-git
and list out the your linked working tree
Solution
➜ foo-bar git:(foo-bar) git worktree list
/home/ThePrimeagen/personal/hello-git 7488b35 [trunk]
/home/ThePrimeagen/personal/foo-bar 7488b35 [foo-bar]
Problem
Delete the working tree with rm -rf
and see if you can discover through
.git
that your worktree no longer is active. Then delete the worktree
Solution
➜ hello-git git:(trunk) rm -rf ../foo-bar
➜ hello-git git:(trunk) git worktree list
/home/ThePrimeagen/personal/hello-git 7488b35 [trunk]
/home/ThePrimeagen/personal/foo-bar 7488b35 [foo-bar] prunable
[foo-bar] prunable
git tells you that foo-bar
is prunable (its been deleted outside of git!)
That means to delete, just run prune
➜ hello-git git:(trunk) git worktree prune
➜ hello-git git:(trunk) git worktree list
/home/ThePrimeagen/personal/hello-git 7488b35 [trunk]
How did git know it was prunable?
➜ hello-git git:(trunk) ls -la .git/worktrees
total 12
drwxrwxr-x 3 mpaulson mpaulson 4096 Mar 20 12:27 .
drwxrwxr-x 9 mpaulson mpaulson 4096 Mar 20 12:27 ..
drwxrwxr-x 3 mpaulson mpaulson 4096 Mar 20 12:27 foo-bar
➜ hello-git git:(trunk) ls -la .git/worktrees/foo-bar
total 32
drwxrwxr-x 3 mpaulson mpaulson 4096 Mar 20 12:27 .
drwxrwxr-x 3 mpaulson mpaulson 4096 Mar 20 12:27 ..
-rw-rw-r-- 1 mpaulson mpaulson 6 Mar 20 12:27 commondir
-rw-rw-r-- 1 mpaulson mpaulson 37 Mar 20 12:27 gitdir
-rw-rw-r-- 1 mpaulson mpaulson 24 Mar 20 12:27 HEAD
-rw-rw-r-- 1 mpaulson mpaulson 289 Mar 20 12:27 index
drwxrwxr-x 2 mpaulson mpaulson 4096 Mar 20 12:27 logs
-rw-rw-r-- 1 mpaulson mpaulson 41 Mar 20 12:27 ORIG_HEAD
the gitdir
no longer exists, therefore we can prune this worktree!
Problem
Recreate a worktree and delete it through .git this time.
Solution
➜ hello-git git:(trunk) git worktree add ../foo-bar
Preparing worktree (checking out 'foo-bar')
HEAD is now at 7488b35 Revert "E"
➜ hello-git git:(trunk) git worktree list
/home/ThePrimeagen/personal/hello-git 7488b35 [trunk]
/home/ThePrimeagen/personal/foo-bar 7488b35 [foo-bar]
➜ hello-git git:(trunk) git worktree remove ../foo-bar
➜ hello-git git:(trunk) git worktree list
/home/ThePrimeagen/personal/hello-git 7488b35 [trunk]
➜ hello-git git:(trunk) ls .. | grep foo-bar
➜ hello-git git:(trunk)
I love worktrees
I think they are one of the best ways to use git as they allow you to keep state based on each branch rather than on directory. This allows for partial changes just to remain partial changes instead of doing the commit, stash, or rebase squash dance.
I would highly recommend getting use to working with them.
Other Downsides
Its not all upside when it comes to git worktrees.
- rust can easily eat up a couple gigs per branch
- npm installing on each branch can take a minute
- go is great
- .env files can be a bit of a pain