stephen.news

hypertext, words and more

How to Delete a Git Commit, Locally and Remote

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.

GIF by Justin - Find & Share on GIPHY

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 navigation or 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 navigation branch:

$ 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 rebase:

$ 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 navigation:

$ 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

The + 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.

Further Reading: