How to Safely Remove Specific Commits from a GitHub Repository History
In Git-based workflows, there are scenarios where you may need to permanently remove specific commits from your repository history—such as when sensitive data was accidentally committed or when cleaning up experimental changes. This guide outlines safe and effective techniques to rewrite Git history while minimizing disruption.
Understanding the Implications
Each Git commit represents a snapshot of your project at a point in time, linked in a chain via parent references. Removing a commit alters this history, which can cause issues for collaborators if the branch has already been shared. Therefore, such operations should only be performed on branches that haven’t been widely distributed—or with explicit coordination among team members.
Prerequisites
- Create a full backup of your repository before proceeding.
- Ensure you have write access to the target branch on the remote (e.g.,
mainordevelop). - Notify collaborators if the branch is shared, as history rewriting will require them to re-clone or reset their local copies.
Method 1: Interactive Rebase for Recent Local Commits
If the commit to remove exists only in your local history and hasn’t been pushed, use interactive rebase:
git rebase -i HEAD~3 # Replace 3 with the number of recent commits to review
In the editor that opens, delete the line corresponding to the commit you wish to remove (or change pick to drop). Save and exit. Git will replay the remaining commits, effectively omittting the targeted one.
Method 2: Filtering History with git filter-repo
For commits already pushed—or when removing files/data across the entire history—the modern and recommended tool is git-filter-repo (a safer successor to the deprecated git filter-branch).
First, install git-filter-repo (e.g., via pip install git-filter-repo). Then, to remove a specific file from all history:
git filter-repo --path path/to/sensitive/file.txt --invert-paths
To remove a commit by message or author pattern:
git filter-repo --commit-callback '
if b"accidental secret" in commit.message:
commit.skip()
'
This command rewrites the entire history, excluding matching commits or files, and automatically updates refs and tags.
Force-Pushing Cleaned History
After rewriting history locally, synchronize with the remote repository using a force push:
git push origin main --force-with-lease
Prefer --force-with-lease over --force to avoid overwriting new changes others may have pushed since your last fetch.
Cleaning Up Remote References
If the repository contained sensitive data, also consider:
- Rotating any exposed credentials or keys.
- Contacting GitHub Support to purge cached objects (for truly sensitive leaks).
- Deleting and recreating the repository if complete sanitization is required (last resort).
Note: Even after history rewriting, Git objects may persist in reflogs or backups temporarily. For high-security scenarios, additional cleanup steps are necessary.