Bird nests? Tree limbs? Summer squash? There sure are a lot of plant, animal and food terms in that title. What are we talking about here, and how is it related to source code repositories?
As the Curator team has grown, so has the concurrent development of features. We’ve found ourselves wanting to build off of a feature that someone else is still working on. In Git, this means we want to branch off of someone else’s branch, and both of them will eventually be merged back into the main branch. To add to this, because our features are relatively complex, each branch often has several commits in them. We don’t like cluttering our commit history, so we squash all of those commits when merging back into the main branch. Furthermore, we also delete those feature development branches as soon as they are merged. If you add everything up here, branching from other branches isn’t going to be so straightforward when it comes time to merge back to main.
A Treehouse Scenario
Let’s say we have a Git repository for building treehouses to keep with our title’s theme. One of our engineers has begun working on the machinery to create the basic structure of a treehouse, but they’re not quite done adding bells and whistles (or chirps and tweets?) to it. Now, let’s say I’m impatient and want to add some machinery to add more stories to that treehouse structure, because quite frankly a treehouse without a penthouse is just slumming it, and I don’t want to wait for the other structure’s branch to be merged. Git will happily let me create a branch from their branch.
As I begin working on my multi-story machinery, they add a few more commits, create a merge request and squash it all back into the main treehouse factory branch. So far, I’m still not affected. I go on to add a few more commits of my own.
Now that I’m ready to create a merge request back to the main, I run into trouble because their branch is gone. As a result, Git tells us that our desire for a straightforward merge has felled … ahem … failed. My earlier impatience has caught up with me and I’m now out on a limb.
The Fix
Git comes with some arboreal tools to cut and graft my branch, so I don’t have to start all over. I just need to rebase my branch from their latest commit onto the main branch. This effectively changes the parent of my branch to where theirs was merged into the main branch.
The complicated part here is that the command to rebase needs to know the last commit of their branch. If that branch was still around, this would be easy because we could just use the branch name. However, since we delete branches as soon as they are merged and squashed to main, that branch no longer exists.
The good news for us is that our merge request system keeps tabs on all of the commits that were in that branch, so this is really easy. If you’re using a Git system that doesn’t keep tabs that way, you’ll probably want to hold off on deleting branches until after this rebase process because finding that commit hash is a little more complicated.
Here are the Git commands to rebase my nested branch:
git checkout multi_story_treehouse git rebase --onto main <their last commit ID before the merge>
Diagrams
Everybody like diagrams, so let me show you some for the key parts of the process. Here’s the state of things just before their branch is merged. As you can see, I greedily branched from their branch just after their first commit (A):
Here’s what it looks like after all of the merging and squashing and deleting of branches for their stuff. Their commits A and B were squashed into what we’ll call commit AB, and their branch was deleted:
Here’s what it looks like after rebasing my branch back to the main. Rebasing doesn’t move the commits; it actually recreates them, so you’ll notice that commits C and D are now C2 and D2. Furthermore, my original branch is discarded and left to wilt away:
Hopefully, these tips will help your development flourish and set you up for a fruitful release.
Too much?