Alright, we’ve all been there. You got some merge conflicts. Or there’s a last minute hotfix. You feverishly make a commit. Then, your worst fear is realized only after you’ve pushed it remotely.
You made a mistake. The horror.
First, don’t sweat it. You have options. Most of the time, when I do this, I just need to remove the commit altogether. Other times, I need to make revisions, and replace that commit altogether. Tabula rasa amirite?
Okay so let’s get started. First, I’m making a few assumptions. I’m doing work on a branch separate from
master. Let’s say you’re working on Navigation, I would be on a branch called
nav-component or something. It’s good practice to keep your work separate, and branched.
Alrighty, with that out of the way, let’s run
git log on our example
$ git log --pretty=oneline --abbrev-commit 32cd83e Made a change with a mistake b9u3cc5 Changed again 105fd3d Updated some content 5cd7718 Fixed the NavBar a1842d4 Initial commit
Alright so looking over our log here, we can see at the top, that commit
32cd83e is the most recent commit. It’s the problematic commit in question. To edit out commit history we are going to use
$ git rebase -i HEAD~2
Before you run this, let’s break down what we’re doing here. A classic use-case of
rebase is when you’re on a branch say,
navigation. But, let’s say that it’s been a week since you branched. It’s likely that
master has diverged since you created your branch. What divergence means, is that the branch (we’re on)
navigation is currently out-of-date and lacks the new history (from the past week) on
master. We don’t have to worry about that here per se.
But, when you work in repos that multiple people contribute to, a branch can be out-of-date very often. If you use
master as the center of truth, the organization can use
rebase to bring other contributors history into your branch, and therefor, up-to-date with
master. Neat huh?
Anyways, you could say what we’re doing here, is similar. Instead of using
rebase to replay work from a branch on top of
master, we want to rewrite the commit history of the branch we’re currently on. After running the command above, you should see something like this:
b9u3cc5 Changed again 32cd83e Made a change with a mistake # Rebase 105fd3d..46cd867 onto 105fd3d # # 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 # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. #
Here, (and admittedly confusingly) the most recent commit,
32cd83e we want to destroy is listed at the bottom instead of the top. You can see pretty clearly what the
HEAD~2 argument does. It flags
rebase to only show the most recent 2 commits in this interactive rebase wizard. Because, that’s all we need to see for context.
So, the options we have now is we can remove that line altogether, and the commit will be dropped into oblivion, as if it never happened. If we remove the problematic commit, and exit the rebase wizard, we can now check our history on
$ git log --pretty=oneline --abbrev-commit b9u3cc5 Changed again 105fd3d Updated some content 5cd7718 Fixed the NavBar a1842d4 Initial commit
Look ma! No mistake!
32cd83e is gone! Now that we have this history locally, let’s push it up, remotely:
$ git push origin +navigation
+ flag before the branch name signals that this will be a force push. Alternatively you could have written the same command as:
$ git push -f origin navigation
I like using this technique, because I enjoy committing everything. Mistakes and all. Because, well for one, why not? Documenting your code history can be helpful sometimes. Besides, ultimately, you can use
rebase to squash and fixup your commit history anyways. So, when it comes time to push to remote, I just take a beat and
git rebase -i HEAD~N to keep my history lean. I’ve only recently learned the pains of not keeping my git history pretty and concise. So, now I’m a convert, and everyone benefits from a readable git history.