How to rewrite Git history with git filter-repo
git filter-repo is the modern replacement for git filter-branch, rewriting repository history up to 50 times faster while providing a cleaner, safer interface with better defaults.
As the creator of CoreUI with 25 years of open-source development experience, I switched all projects to git filter-repo the moment it became the officially recommended tool in Git documentation.
It is not bundled with Git — install it separately — but its performance and safety features make it the clear choice for any history rewriting task.
Always work on a fresh clone before running it, as the operation cannot be undone once complete.
Install git filter-repo and remove a specific file from all history.
# Install (macOS)
brew install git-filter-repo
# Install (pip)
pip install git-filter-repo
# Verify
git filter-repo --version
# Clone a fresh copy first (filter-repo refuses to run on repos with a remote unless forced)
git clone --no-local original-repo clean-repo
cd clean-repo
# Remove a specific file from entire history
git filter-repo --path secrets.env --invert-paths
--path secrets.env selects the file. --invert-paths inverts the selection — keep everything EXCEPT the specified path. The result is a repository where secrets.env never existed in any commit.
Remove a Directory from History
Delete an entire folder from every commit.
# Remove the dist/ directory from all commits
git filter-repo --path dist/ --invert-paths
# Remove multiple paths at once
git filter-repo \
--path dist/ \
--path node_modules/ \
--path coverage/ \
--invert-paths
Listing multiple --path options removes all of them in a single pass. This is much faster than running filter-branch separately for each path.
Remove Files by Pattern
Delete files matching a glob or regex.
# Remove all .env files
git filter-repo --path-glob '*.env' --invert-paths
# Remove all files matching a regex
git filter-repo --path-regex '.*\.(log|tmp|bak)$' --invert-paths
--path-glob uses shell glob syntax. --path-regex uses Python regex syntax. These are useful for removing all files of a certain type without listing each one individually.
Update Author Email Across History
Replace incorrect committer emails globally.
git filter-repo --email-callback '
return email if email != b"[email protected]" else b"[email protected]"
'
The --email-callback receives and returns bytes. Python’s conditional expression a if condition else b makes the replacement concise. Similar callbacks exist for --name-callback, --message-callback, and --commit-callback for full control over every commit attribute.
Push the Rewritten History
Force-push all branches and tags after rewriting.
# Re-add the remote (filter-repo removes it by default)
git remote add origin https://github.com/user/repo.git
# Push all branches
git push origin --force --all
# Push all tags
git push origin --force --tags
git filter-repo removes all configured remotes as a safety measure to prevent accidental pushes. Re-add the remote explicitly and force-push after verifying the rewritten history looks correct with git log --oneline.
Best Practice Note
After pushing rewritten history, all collaborators must re-clone the repository — their local branches diverge from the rewritten history and cannot be cleanly merged. Coordinate with your team before running this operation. For the legacy approach when git filter-repo is not available, see how to rewrite Git history with filter-branch. For removing sensitive data specifically, GitHub and GitLab may also cache content in pull request diffs — contact their support to purge those caches after cleaning your history.



