Progress:
TIER 2 · MODULE 04· Intermediate

git filter-branch

Don't. Use git filter-repo. Listed for completeness.

🎯 What & why

git filter-branch rewrites history by replaying every commit through user-supplied filters. It is deprecated as of Git 2.24: slow, riddled with sharp edges, and the man page itself tells you to use git filter-repo instead. It is documented here so you recognize it in old scripts; do not write new ones.

🧠 Mental model

Reads from refs and objects, writes a parallel set of rewritten objects, and updates refs to point at them. Backs up originals under refs/original/. The working tree and index are clobbered repeatedly during --tree-filter runs because it checks out every single commit.

🛠️ Synopsis

git filter-branch [--setup <command>] [--subdirectory-filter <directory>]
        [--env-filter <command>] [--tree-filter <command>]
        [--index-filter <command>] [--parent-filter <command>]
        [--msg-filter <command>] [--commit-filter <command>]
        [--tag-name-filter <command>] [--prune-empty]
        [--original <namespace>] [-d <directory>] [-f | --force]
        [--state-branch <branch>] [--] [<rev-list options>...]

🎚️ Switches & options

FlagWhat it does
--tree-filter <cmd>⚠️ Check out every commit, run <cmd> against the working tree, then re-stage. Catastrophically slow on real repos; this is the option that gives filter-branch its bad reputation.
--index-filter <cmd>Run <cmd> against the index without checking out files. Orders of magnitude faster than --tree-filter for path removals.
--commit-filter <cmd>⚠️ Replace the default git commit-tree with <cmd>. Powerful and dangerous: any bug here corrupts every commit on the rewrite.
--env-filter <cmd>Modify environment variables (author/committer name, email, date) per commit. The standard way to fix bad identities.
--subdirectory-filter <dir>Treat <dir> as the new repo root, dropping everything else. Common for splitting a subproject out.
--prune-emptyDrop commits that become empty after filtering. Almost always what you want when removing files from history.
--tag-name-filter <cmd>Rewrite tag names through <cmd>. Without this, tags keep pointing at the original commits and look ghostly.

💡 Use cases

🧪 Examples

DEPRECATED: remove a leaked secret
# Don't actually do this. Use `git filter-repo` (see recommendations).
$ git filter-branch --force --index-filter \
    'git rm --cached --ignore-unmatch path/to/secret' \
    --prune-empty --tag-name-filter cat -- --all
DEPRECATED: extract a subdirectory
$ git filter-branch --prune-empty --subdirectory-filter src/lib HEAD
DEPRECATED: rewrite author email
$ git filter-branch --env-filter '
  if [ "$GIT_AUTHOR_EMAIL" = "old@example.com" ]; then
    export GIT_AUTHOR_EMAIL="new@example.com"
    export GIT_COMMITTER_EMAIL="new@example.com"
  fi
' --tag-name-filter cat -- --branches --tags
Modern equivalent (do this instead)
$ pip install git-filter-repo
$ git filter-repo --path path/to/secret --invert-paths
$ git filter-repo --subdirectory-filter src/lib
$ git filter-repo --mailmap mailmap.txt

🎓 Recommendations

🪤 Common pitfalls

🔗 Related modules

📝 Quiz

Hit each option, then Check answers. Score is recorded; Next is always open.