Accidentally committing a wp-config.php with production credentials, an .env file, or a private key requires a proper history rewrite — not just a new commit that deletes the file. git filter-repo (the official replacement for git filter-branch) is orders of magnitude faster, safer, and more ergonomic for removing files, rewriting commit messages, and transplanting subdirectories into or out of a repository.
Problem: A WordPress plugin repository was accidentally committed with API keys, password hashes, or binary build artifacts in the history — the sensitive data is still accessible in old commits even after the files are deleted.
Solution: Use git filter-repo (the modern replacement for git filter-branch) to rewrite history: git filter-repo --path secrets.env --invert-paths removes a file from all commits. For content-based removal, use --replace-text replacements.txt. Force-push the rewritten history, invalidate all existing forks, and rotate any exposed credentials immediately — the old commits remain accessible to anyone who cloned before the rewrite.
The commands below remove a sensitive file from all history, strip credentials from a specific commit range, extract a plugin subdirectory into its own repository, and verify no traces remain before force-pushing.
# ── 0. Install git filter-repo ────────────────────────────────────────────
pip3 install git-filter-repo
# or: brew install git-filter-repo
# ── 1. Remove a file from ALL history ─────────────────────────────────────
# ALWAYS work on a fresh clone — filter-repo rewrites all SHAs
git clone --no-local /path/to/original-repo /tmp/clean-repo
cd /tmp/clean-repo
git filter-repo --path wp-config.php --invert-paths
# --invert-paths = "remove everything that matches --path"
# Remove multiple sensitive files at once:
git filter-repo \
--path .env \
--path wp-config.php \
--path config/secrets.php \
--invert-paths
# ── 2. Remove a file only from commits before a date ──────────────────────
git filter-repo \
--path private-key.pem \
--invert-paths \
--commit-callback '
if commit.committer_date < b"2025-01-01T00:00:00+00:00":
commit.skip()
'
# ── 3. Replace a string in all file contents (scrub a hardcoded password) ─
git filter-repo --replace-text <(echo 'password123==>REDACTED')
# Or from a file:
# echo 'literal:db_password_here==>***REMOVED***' > replacements.txt
# git filter-repo --replace-text replacements.txt
# ── 4. Verify no traces remain ────────────────────────────────────────────
git log --all --full-history -- wp-config.php # should return nothing
git grep 'password123' $(git rev-list --all) # should return nothing
# ── 5. Force-push all branches and tags ───────────────────────────────────
git remote add origin git@github.com:org/repo.git
git push origin --force --all
git push origin --force --tags
# All collaborators must re-clone — old clones have the sensitive data!
# Invalidate the leaked credentials immediately (rotate API keys, DB passwords)
# ── 6. Extract a subdirectory into a new repository ──────────────────────
git clone --no-local /path/to/monorepo /tmp/plugin-only
cd /tmp/plugin-only
git filter-repo --subdirectory-filter wp-content/plugins/my-plugin
# Result: the repo root IS the plugin directory; all history preserved
NOTE: git filter-repo rewrites every commit SHA in the repository — after force-pushing, every team member's local clone is invalid; they must delete their local clone and re-clone; there is no safe git pull path after a history rewrite, and any open pull requests referencing the old SHAs will be broken and must be re-created.