Git Rebase Is The Pikmin Of Source Control

Git Rebase Is The Pikmin Of Source Control

git isn’t easy to learn, and once you do, the git rebase command seems to invoke fear and confusion in many people.  But once you learn how to use it effectively, you’ll wish you had learned it sooner–both for your solo projects and while working on a fast moving development team.

I’m going to use Pikmin to describe git rebase for you.  Not only will it make things fun, but it also expertly illustrates what you’ll be doing with your code: manipulating it to achieve a certain goal, just like you’d do in the video game.

And if you’re anything like me, you’ve tried to read about rebasing but never really grasped the concept or you’ve glossed over as you’ve looked at images of branches that try to describe the process.

To that end, think of each commit as a unique Pikmin (it will really help if you’ve played the game before)–a special little creature that you can control, manipulate, and order around.  You can send a single one to do a single task, or you can combine multiple Pikmin to do something bigger.  And if you change your mind, that’s just fine, too.  Just like writing code, what you commit can be changed whenever you need to better accomplish what you’re setting out to do.

I’ll be using a real life example–a pull request I made for Ansible, which will show what you can do with git rebase.  So if you’ve read this far, I’ll assume you know what a commit is and understand the basics of how git works (add, commit, push).

When I first opened my pull request, I had a single commit (my working inventory script), which you can see in my git log.

As it turns out, I missed a PEP8/pylint error and had to add a second commit, which fixed it.  Just like the different kind of Pikmin, this is a unique commit and has its own thing that it does (in this case, it fixes a small linter error).

Now my git log has two unique Pikmin (commits).

Later, some community members found a bug in my code.  I had to fix that so I pushed another Pikmin (commit) to fix the issue.  This is, again, a unique commit, that does a unique thing.

Finally, Ansible had added some more requirements to their PRs since I had initially submitted it, so I needed–yet again–another commit.

A unique commit to do a unique thing, just like each individual Pikmin.

So now my PR is sitting out there with four unique commits (Pikmin).  There’s nothing wrong with that and it shows a nice progression of my code.  At some point though, I might want to do something else with those commits.

Let’s say a few months have passed and Ansible wanted to merge it in.  Since I’m contributing all new code, it wouldn’t really make a difference if they merged it in as is, but perhaps–like many projects out there–they want their PRs rebased onto to the latest commit before it can be merged.

All this means is that they want their latest commit to be just before your changes.  Literally, putting the base of their branch at the start of yours.  So if I rebased my branch onto theirs, it would make merging easy for them since all of the code that had been developed over those two months, would be right below my additions in the git log.  It would also allow them to test it in the current state of development because my branch would have all of their latest code, plus the new feature I was looking to add.

This will make more sense with some pictures of Pikmin.

As it stands now, my branch is sitting at a base commit that is about two months old (July 5, 2019).  This is a unique commit made by someone other than me.  It doesn’t matter what it is, just that it’s written by someone else, it’s unique, and it’s out of date.

Like I said before, this doesn’t pose a huge problem since I am only contributing a single new file that doesn’t conflict with anything.  But maybe I want to make sure I have all of Ansible’s changes so I can verify my code still works after so much time has passed.

Well, that’s what git rebase can help you do when working with a team of other developers: you write your code, then integrate their changes to the tip of your branch, and then you’re always up-to-date with what they are working on.

So if I issue the command:

git rebase -i upstream/devel

to interactively rebase Ansible’s upstream/devel branch onto my local branch, I get presented with this window.

There’s a lot of text there, but you mainly need to be concerned with the four commits that I have been discussing.  You’ll notice they are in reverse order of the git log I have been showing you.  This is another piece that makes rebasing very confusing.

In this menu, the commits are processed starting at the top and working down.  The first commit to be processed will be my inventory script.  The next one will be my linter fix, then my bug fix, and finally my boilerplate changes requested by Ansible.

This interface can be very confusing if you’ve never used VIM before.  This is another barrier to learning how to use git rebase.  Assuming you don’t know about VIM, I can simply type :wq (write and save) to finish the rebase.

Since there were no conflicts, the rebase succeeds, and my commits are now placed on top of the latest commit made by Ansible (from September 21, 2019) and I have all their latest code with my changes right on top.  Magic!

That’s one way to use git rebase: integrating someone else’s changes into yours and keeping your branch up to date with a remote branch.

The other useful way to use git rebase is to manipulate your own local commits to merge, meld, and reorganize them.

Pretend for a moment, I closed my PR to make some additional changes but I wanted to start with all of the bug fixes in place and then add functionality on top of that.  Essentially, I want to start from a known good point as a single commit that I can go back to.

I can still stay on my existing branch to do this and if I do  git rebase -i HEAD~4, which just does a rebase of the last four commits, I get prompted with the same window we saw before.

However, if I instead change the word pick to fixup (or f for short), I can pick my initial inventory script commit, and then the other three commits will (per the readme) be squashed into the previous commit and have their commit messages discarded.

Essentially, I’m picking my first commit, and melding all the other ones into it so it looks like I did all this work under a single commit.  Now I’m left with a single new commit that contains all my bug fixes.

Now this scenario is all about when rebasing goes well and is uneventful.  My next post will be about how to deal with conflicts, force pushing, and dealing with development churn.