Posted on 6 mins read

Introduction

There are times where you might be working from a particular git branch and need to quickly jump over to a different branch to do some urgent work.

Typically you would need to first git stash anything you were working on (as it’s unlikely to be in a state where it can be committed), and then you’d have to leave your current branch to create a new branch from master and thus begin working on your new urgent task.

This is a fairly straightforward workflow, but there is a mild annoyance which is that I happen to git stash a lot and I find when jumping over to a new branch to do some urgent work that I might end up git stash‘ing a few more times along the way.

Ultimately, when I’m done with my urgent task and ready to go back to my other branch, I then have to sift through my stash to find the relevant one I want to pop. OK so not that tragic considering git stash list will indicate the branch on which the stash was taken (which helps), but I do then need to Google what the syntax is for popping a specific stash (e.g. it’s git stash apply stash@{n} where n is the index you want to apply.)

Note: for the life of me I wish I could remember the syntax but it just eludes me every time.

Oh, and then you have to think about whether you actually want to use apply, which leaves the stashed changes in the stack, or if you meant to actually pop the stashed content (git stash pop stash@{n}) so it’s properly removed from the stack.

This is where I was recently introduced to a concept in git referred to as a ‘worktree’ (thanks Kiran).

Worktree

Git offers a feature referred to as a worktree, and what it does is allow you to have multiple branches running at the same time.

It does this by creating a new directory for you with a copy of your git repository that is synced between the two directories where they are stored.

This is different to manually creating a new directory and git cloning your repo down, because with the worktree model the two sub directories are aware of each other.

Note: as you’ll see, although this workflow is pretty cool, you could argue that git stash is just plain simpler and easier for a human mind to reason about. I’ll leave that up to the reader to decide.

Example

In the following example I’m going to create a new git repo. I’ll make a change in master, then create a new branch for doing some work. We’ll then imagine that I have been given an urgent task that I must complete now and yet my current non-master branch is in such a state that I want to avoid just stashing everything.

Note: I use tmux to split my terminal into multiple windows, and this demonstration will require two windows (or two separate terminal instances if you’re not using a screen multiplexer) for the sake of demonstration.

Create a new repo

  • mkdir foo_project
  • cd foo_project
  • touch foo
  • git add foo
  • git commit -m "created foo file"

Create a new branch

  • git checkout -b foo_contents
  • echo 123 > foo
  • git add -u
  • git commit -m "added content to foo"

Now I’ll create a new file and stage it for committing, but I won’t commit it (this is where we pretend my branch is in some hideously complex state).

Create new worktree branch

  • git worktree add ../foo_hotfix

Note: you’ll want to create the new worktree in a directory outside of your current repo’s directory (just so there’s a clear distinction).

At this point you’ll find your current terminal is still in the same foo_contents, but there is now a new directory called foo_hotfix outside your current repo’s directory.

Make changes in new worktree branch

Open up a new terminal (or split window) and run through the following steps:

  • cd ./foo_hotfix (or cd ../foo_hotfix if your new terminal is currently set to your main git repo directory)
  • git log

OK, so if you do a git log you’ll find that the worktree has a branch automatically created and named after the worktree (so the branch is called foo_hotfix in my case).

The important thing to realize is that git worktree add is a bit like git branch in that it creates the new worktree from the current branch you’re in. Meaning that my foo_hotfix branch has the “added content to foo” commit from the foo_contents branch as that’s where I ran the git worktree add command from.

This is what git log looks like for me in this new worktree:

* d374dcb (Integralist) - (HEAD -> foo_hotfix, foo_contents) added content to foo (2 minutes ago)
* 9ae3a7f (Integralist) - (master) created foo file (3 minutes ago)

I don’t want the commit d374dcb in there as it’s coming from a branch (foo_contents) that’s still in progress, and so I’ll need to rebase out that commit:

  • git rebase -i 9ae3a7f

Note: the rebase editor opens and I change pick to drop to get rid of the commit.

Now at this point I have a new working directory that I can work in:

  • echo hotfix > baz
  • git add baz
  • git commit -m "some hotfix"

Merge my hotfix back into master

I’m going to change into my master branch, but remember I’m still in the foo_hotfix directory, so my main repo directory foo_project (open in another terminal window) is still in the foo_contents branch).

  • git checkout master
  • git merge foo_hotfix

Removing the worktree

OK, so at this point we’ve merged our hotfix into master. I want to go back to my original repo directory and make sure I have the latest master rebased in before continuing on with my foo_contents work.

To remove the worktree you can either remove it using the git interface (e.g. git worktree remove foo_hotfix) or manually remove it (e.g. cd ../ && rm ./foo_hotfix), where git will, at some point in the future, internally run a prune and remove any references to this orphaned branch/working tree (you could also manually trigger that prune using git worktree prune).

Note: if I do git worktree remove foo_hotfix while currently residing inside the foo_hotfix directory, I’ll find that the .git repository is removed from the directory.

Continuing working on my feature branch

Presuming I’m still in the foo_hotfix directory and that’s where I ran git worktree remove foo_hotfix:

  • cd ../foo_project
  • git rebase master < whoops! I need to stash my changes first †
  • git stash pop

† why yes, this does seem a bit strange considering that’s what I was trying to avoid in the first place, but in this case it’s a single ‘stash’ and so a simple git stash pop will suffice to get me back to where I need to be.

I can now continue working on my foo_contents branch.

Conclusion

Well, this was fun heh! 😉

Do you think you have any uses for git’s worktree feature?

Let me know on twitter.


But before we wrap up... time (once again) for some self-promotion 🙊