Multiple Branches in Git
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
(orcd ../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
todrop
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 thefoo_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 🙊