Purging commits from Git History

6 min read
Table of Contents

There are several ways to remove a bad commit from Git history. This article outlines the method that makes the most sense to me, and is a flexible approach whether you’re removing a commit from N commits ago or the most recent commits.

Why remove commits at all? As one example, you might accidentally commit a personal access token to Git history and push it up to a public repo. I’m sure I and many other developers have done something like this before.

This is why I’ve written this article. It’s an easy problem to fix if you know just a few basics with the git rebase command.

Enter: rebase

There are great explanations on what the Git subcommand rebase is and how it works. I’ll try a metaphor to make the explanation simpler.

Imagine a movie script that follows a complex series of events. In the original version of the movie, there’s a character revelation that explains motivations for their actions. In editing the script, we instead realize that this revelation is best suited towards the end of the movie.

So we take that character scene, it’s about two minutes long, cut it, and push it to the end of the movie. We’ve extracted an event from the film, and affixed it to the end of the plot to improve the movie’s flow.

That’s the gist of rebase. It’s a tool to rewrite history of a project. It allows us to snip out parts we don’t like and re-arrange history as needed.

Start your rebasing

How do we get started? Let’s say our commit history looks like this:

* 8af4417 (HEAD -> main) More great text
* f39e19a Added more text
* e226b1f Added sensitive information, oops
* 2147765 (origin/main, origin/HEAD) Coworker's email
* 4a17b7a Correct user name, false email

Commit e226b1f added some sensitive data which we need to remove from our Git history. On the command line type:

git rebase -i e226b1f^

This ^ here means we start the rebase “from the commit’s parent.” In plainspeak, think of this as saying: “let’s review the Git history including and chronologically after the commit `e226b1f”

After running this command, your editor should open a file that looks like the below. I typically use the embedded terminal in VS Code. So after running git rebase -i e226b1f^ a new editor window opens right away with a file that needs editing. It should look something like this:

pick e226b1f Added sensitive information, oops
pick f39e19a Added more text
pick 8af4417 More great text
# Rebase 2147765..8af4417 onto 2147765 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
[...]

Removing the bad commit

The above file which Git creates is what we refer to as an “interactive rebase.” Git provides a file that gives us a lot of flexibility for how to alter history.

For my purposes, I pretty much only need to remove bad commits. There’s plenty more you can do with rebasing. For now we’ll just focus on removing, i.e. dropping commits.

With the opened file, we simply prepend the word drop to the commit we don’t want.

drop e226b1f Added sensitive information, oops
pick f39e19a Added more text
pick 8af4417 More great text

Notice above our bad commit e226b1f now has the word drop in front of it.

After making this change, simply close your IDE’s editor and Git will proceed with next steps.

Righting history

You’ll now notice that your Git command line will say something like:

❯ git rebase -i e226b1f^
Auto-merging text.md
CONFLICT (content): Merge conflict in text.md
error: could not apply f39e19a... Added more text
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply f39e19a... Added more text

We now have a merge conflict. Why? Git needs explicit instruction on how to reconcile the changes after we’ve removed the commit from history.

Resolving the Merge Conflict

After dropping the e226b1f commit and proceeding with the rebase, you encountered a merge conflict in text.md. Here’s how to resolve it:

  1. Identify the Conflict  

The error message indicates a conflict in text.md. The conflict arises because Git is trying to apply the changes from the f39e19a commit on top of the new base (which no longer includes e226b1f). If f39e19a depended on changes made in e226b1f, Git can’t automatically reconcile the differences.

  1. Open VS Code’s Merge Editor  

Since you’re using VS Code with the Git extension enabled:

  • Navigate to the Source Control panel.

  • Double-click the conflicted file (text.md) to open it.

  • Select Open Merge Editor in the bottom-right corner.        This launches the 3-way merge editor, displaying:

  • Incoming changes (left panel)

  • Current changes (right panel)

  • Result (bottom panel)

  1. Resolve the Conflict  
  • Inspect the Conflict:  

The merge editor highlights conflicting lines. For example:

     ```diff      <<<<<<< Incoming      Added more text      =======

     More great text      >>>>>>> Current      ```

  • Accept Changes:   Use the CodeLens buttons (e.g., Accept Incoming, Accept Current) or manually edit the Result panel to merge the desired content.  

   - Complete the Merge:  

     Once resolved, click Complete Merge to stage the file and close the editor.

  1. Stage and Continue the Rebase  

After resolving the conflict:

Terminal window
git add text.md # Stage the resolved file
git rebase --continue # Resume the rebase

The rebase will now apply the remaining commit (8af4417).

  1. Finalize the Rebase

If no further conflicts arise, the rebase completes successfully. Your history is now clean, with the e226b1f commit removed.

Pushing to GitHub

After rewriting history, force-push your changes to update the remote repository:

Terminal window
git push --force-with-lease origin main

Key Notes:

  • Force Push Risks: This alters commit history, which can disrupt collaborators who have based work on the original timeline. Always communicate with your team before force-pushing 1 2.

  • Best Practice: Use --force-with-lease instead of --force to prevent overwriting recent changes that might have been pushed by others 1 3.

  • Important for Sensitive Data: If you’re removing commits that contain sensitive information (like API tokens or passwords), be aware that the data may still exist in GitHub’s cache, pull request history, or in forks of your repository. For truly sensitive data, you should also rotate/revoke the exposed credentials and consider using tools like BFG Repo-Cleaner or git-filter-repo for more thorough history rewriting.

This process ensures the sensitive commit is purged from history while maintaining a clean repository state.

My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts