Skip to content

Git Filter-Branch — Remove File from History Fix

DodaTech Updated 2026-06-24 3 min read

In this tutorial, you'll learn about Git Filter. We cover key concepts, practical examples, and best practices.

You accidentally committed a sensitive file (password, API key, large binary) that was pushed. Simply deleting it in a new commit does not remove it from history — anyone can still access it. git filter-branch rewrites history to purge it permanently.

The Problem

# Accidentally committed .env with secrets
git add .env
git commit -m "Add config"
git push origin main

Even after deleting and pushing:

git rm .env
git commit -m "Remove .env"
git push origin main

The file is still accessible via git show <old-commit>:.env.

Wrong Approach

# WRONG — git rm does not remove from history
git rm .env
git commit -m "Remove .env"

Right Approach

# Remove the file from all history
git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch .env' \
  --prune-empty --tag-name-filter cat -- --all

Expected output:

Rewrite abc123def4567890 (1/15) (0 seconds passed)
Rewrite ... (15/15) (3 seconds passed)

Ref 'refs/heads/main' was rewritten

Step-by-Step Fix

Step 1: Create a backup

git clone --mirror . /tmp/repo-backup.git

Step 2: Run filter-branch to remove the file

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch .env' \
  --prune-empty --tag-name-filter cat -- --all

Step 3: Verify the file is gone

git log --all -- .env

Should show no commits.

Step 4: Force push rewritten history

git push origin --force --all
git push origin --force --tags

Step 5: Clean up local references

git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git reflog expire --expire-unreachable=now --all
git gc --prune=now
pip install git-filter-repo
git filter-repo --path .env --invert-paths

Prevention Tips

  • Add sensitive files to .gitignore before the first commit
  • Use .gitignore templates for your language/framework
  • Set up pre-commit hooks to detect secrets (e.g., git-secrets)
  • Use environment variables with .env.example (never commit actual .env)
  • Scan history periodically with truffleHog or gittyleak

Common Mistakes with filter branch

  1. Mixing let bindings with <- bindings in do notation, producing type errors
  2. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  3. Non-exhaustive pattern matches that compile with warnings then crash at runtime

These mistakes appear frequently in real-world GIT code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### What is the difference between git filter-branch and git filter-repo?

git filter-branch is the built-in but deprecated tool for rewriting history. git filter-repo is a faster, safer, and more feature-rich replacement. Git recommends using git filter-repo for all new history rewriting.

How do I remove a file from only the most recent commits without rewriting all history?

If the file only exists in recent commits, use git filter-branch with a commit range: git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch .env' HEAD~10..HEAD. This limits history rewriting to the last 10 commits.

Will filter-branch change commit SHAs for all commits?

Yes. Every commit from the first occurrence of the target commit onward gets a new SHA because the tree hash changes. This is why force push is required. All collaborators must rebase their branches on the new history.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro