Easy Uses for Git Rebase
November 29, 2015
Git is a powerful and useful tool, but let's face it, at some point you're going to mess up. Or, if you don't, at some point someone else will, and you'll have to fix it. Below is what I've learned about using rebase to fix these problems.
These problems usually take the form of:
- Committing code on the wrong branch
- Pushing incorrect commits to a remote repo
- Merging a branch that wasn't quite ready
I'll assume you have a basic understanding of git—just committing and pushing code.
Rebasing is kind of a mind-numbing topic. Explanations of it are always filled with elaborate diagrams that really never make much sense to me. There's a pretty good explanation here, but in a nutshell, rebasing edits the commit history and makes new commits out of the old ones. But moving on!
Open a blank file and save some sentence inside of it like,
Vanilla is the best flavor of icecream and commit it. I called mine
README.md with a commit message of
Next, push it up to a remote repository.
Now, let's say someone changes "vanilla" to "chocolate" (which is obviously wrong), and pushes it up.
A mistake has been pushed to
master! (oh noes)
Now this is a simple example, so you could easily change it back to "vanilla" and push to master, but in a real-world scenario, this could mean dozens of commits and files changed. Taking care of them in a one-by-one basis wouldn't be feasible.
So, rebase to the rescue!
Before we move on, make sure you're up-to-date:
git pull origin master.
The first thing you want to find is the SHA of the commit that is one before the last good commit you want to keep. It may take a couple times to figure out which commit is the right one to use, but this will make sense once you see it in action.
To see a history of your commits, type
git log. Here's my output:
john:~/Dropbox/blog/learning_to_rebase (master)$ git log commit d583095fb7e1bd4bf0b2511455b40d7cc3563c0a Author: John Mosesman <firstname.lastname@example.org> Date: Sun Nov 29 11:52:43 2015 -0600 *Chocolate* is the best flavor of icecream commit 30b4442f9ae5c0fc3cd590892f81625bbee25dd4 Author: John Mosesman <email@example.com> Date: Sun Nov 29 11:52:43 2015 -0600 Vanilla is the best flavor of icecream commit 4ef8d1cb697f9301085d7b31e5328132ed3333c8 Author: John Mosesman <firstname.lastname@example.org> Date: Sat Nov 28 23:51:16 2015 -0600 Create README.md ... john:~/Dropbox/blog/learning_to_rebase (master)$
The last good commit before the problem is the one titled "Vanilla is the best flavor of icecream." We want to go to the one before that, which is "Create README.md", and it's SHA begins with
Doing the Rebase
To start rebasing at that point in time, we pass the full SHA of the commit and the
-i flag for interactive mode.
$ git rebase -i 4ef8d1cb697f9301085d7b31e5328132ed3333c8 # Your SHA here
Below is the output. Notice that we have all of the commits after the one we've chosen (the good commit and the bad one). The reason we start with one before the good commit is because of this line in the instructions below: "However, if you remove everything, the rebase will be aborted." Basically, we have to keep at least one commit—we can't just delete the bad ones.
pick d583095 *Chocolate* is the best flavor of icecream pick 30b4442 Vanilla is the best flavor of icecream # Rebase 4ef8d1c..2728a63 onto 4ef8d1c ( 2 TODO item(s)) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out
Notice the various key words and what they do. The only one we're going to use here is
pick tells git which commits to keep and use when re-applying the commits.
To remove our mistake here, all we have to do is delete the line that says
*Chocolate* is the best flavor of icecream.
By default my editor is
vim—which is a large topic in and of itself—but to speed through this, you can use
k to move between lines, and type
dd to delete the line you want. Afterwards,
:wq will write and quit the file (if you want to do a quick tutorial of Vim, just type
vimtutor in any shell).
:wq the file should look like this:
pick 30b4442 Vanilla is the best flavor of icecream # Rebase 4ef8d1c..2728a63 onto 4ef8d1c ( 2 TODO item(s)) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message ...
:wq, you should see the following message in your shell:
Successfully rebased and updated refs/heads/master. If we type
git log again we'll see that the commit is gone! To correct
master on the remote repository we just need to do a force push:
git push -f origin master (side note: it's always scary to do a
And that's it!
Bonus Feature: Squash/Fixup
As a side note, let's say you're editing a README and you find a grammar or spelling change, commit it, and push it up. After that, you find another one, and do the same. You could do this several times, and end up with five different commits—all editing the same file with the same purpose. While this isn't bad, persay, it does bloat the repository when really all of those commits were one idea.
While rebasing, you can use
fixup to combine all of these commits into one. Just a way to keep the repository history a little cleaner!