Today was full of pulling and fetching and branching and merging and commiting and, yes, the dreaded rebasing. I’ll admit: before today, though I’d read about it on multiple occasions, rebasing in Git still made my brain physically hurt. And even as today wore on, I found myself more and more perplexed by this scary, repo-busting command.
Until I had an epiphany.
Git rebasing makes sense when you realize that it does exactly what the name says it’ll do. Stick with me here.
Let’s, for a second, imagine we have a repo that looks something like this:
Now, let’s throw out all we know about rebasing (or think we know about rebasing), and just concentrate on what the command actually says. If we rebase
master, we are literally changing the base (or parent!) of
Let’s look at the repo again, but this time with a bit more descriptors:
As it stands,
feature begins it’s life as a child of our second commit. If we
rebase it onto
master we are essentially saying, “Nope, let’s rewrite history. Let’s pretend that
feature began it’s life as a child of our sixth commit (the most current commit in the
master branch).” So to make this rebase happen, we run the following commands:
What Git does, to rewrite history, is take every commit between C2 and C6 (inclusive) and apply it to the
feature branch. Because, for the
feature branch to have a new base, it needs to know the whole history of that particlar base (parent).
feature needs to get caught up. And for that to happen, it needs to “know” everything the
master branch “knows” up until the current moment. And how can that happen? Git has to, one by one, apply every change (commit) in
master’s history to
Now, our repo looks like this:
Woah! Now we have a nice clean history (it’s all linear and stuff!), but
feature is ahead of
master. Why is that?
This is what initially confused the heck out of me, once I thought I understood rebasing. There’s two things to keep in mind for this to make sense:
- The whole “rebase onto” nomenclature is, in and of itself, somewhat confusing.
- If we hadn’t rebased or merged, the
masterbranch would never have any concept of the
featurebranch after our second commit.
Let’s tackle these points one at a time.
1) The whole “rebase onto” nomenclature is, in and of itself, somewhat confusing.
Here’s the thing. Everything I’ve written before this is based upon the fact that the
rebase command actually does what it’s name implies. Cool. However, when we add the word “onto” into the mix, things get confusing. To me,
rebase onto (combined with the myriad descriptions of rebasing as the rewinding of and playing back of commits onto one branch or another) makes it seem as if the branch being, erm, rebased onto, is being acted upon in some way.
2) If we hadn’t rebased or merged, the
master branch would never have any concept of the
feature branch after our second commit.
Let’s look at another version of our original repo, but with some files added to the mix. (Each time you see a file name next to a commit, this means the file was created and commited in that commit. These are simplistic commits for explanation purposes.)
Cool. Now let’s look at the same repository, but this time with the files carried through from one commit to the next. In other words, now we’ll look at what files each commit knows about:
Now, if we created a new branch off of
master right now, what files would it inherit?
If you said files 1, 2, 3, 8, 9, and 10, then you’d be right.
So if we rebase a branch onto
master now, it too would know about those files. (It’s new parent is our current
master branch, and thus inherits everything from that branch.)
But if you notice from that image above, our
master branch has no clue that files 4, 5, 6, and 7 exist. Rebasing our
feature branch, which does know about those files, onto
master just means that now
feature knows about files 1, 2, 3, 8, 9, and 10, just as any new branch would at this point!
Aha! So in order to catch
master up on all the lovely things that we’ve been working on in
feature branch, we still need to merge after the rebase is complete. And then, we get this:
But, bonus side effect: we don’t have to worry about conflicts. Sweet.
Phew. Git rocks.