How do you use git filter-repo to strip a path from every commit in a repo?
answer
- path selection is inclusive by default
- one flag flips keep into drop
- it insists on a fresh clone
- it rewrites tags and refs too
- every downstream SHA is new
basics
~20 sRun git filter-repo on a fresh clone with a path filter: git filter-repo --path secrets/ --invert-paths keeps everything except that path. It rewrites every commit, so all downstream SHAs change and the result must be force-pushed.
solid answer
~40 s`git filter-repo` is the maintained rewriting tool (a separate program, not shipped inside Git). Path selection is positive by default: `--path <p>`, `--path-glob`, and `--path-regex` say *keep only these*; adding `--invert-paths` flips it to *drop these*. So `git filter-repo --path secrets/creds.json --invert-paths` removes that file from every commit and every ref in one pass. It refuses to run unless the repository is a fresh clone (or you pass `--force`), because a rewrite is destructive and unrecoverable without a pristine copy. Afterwards it prunes commits that became empty, rewrites tags to point at the new commits, records the old-to-new mapping in `.git/filter-repo/commit-map`, expires reflogs, runs gc, and removes the `origin` remote so you cannot casually push a half-understood result. Every commit from the first rewritten one onward has a new SHA.
code
bash · 5 linesgit clone --no-local /path/to/repo repo-rewrite
cd repo-rewrite
git filter-repo --path config/prod.env --invert-paths
git log --all --oneline -- config/prod.env # expect no output
git remote add origin [email protected]:team/repo.gitgo deeper
Know that the tool exists and is the recommended way to remove a file from all of history, and that the SHAs change afterwards.
Explain that path arguments select what to keep and that --invert-paths flips that, and describe the fresh-clone guard, the tag and ref rewriting, and the commit-map output.
Show that you plan the aftermath: which external systems store SHAs, how tags and signatures are affected, how you verify the rewrite, and how the team's clones get replaced.
Weigh whether a repo-wide rewrite is worth the coordination cost at all, versus splitting, archiving, or leaving history intact and preventing recurrence.
## What the tool is `git filter-repo` is a single-purpose history-rewriting program. It is not a builtin — it ships separately from Git and is invoked as `git filter-repo`. It reads the whole object stream once, applies your filters, and writes a new history. That single-pass design is why it finishes in seconds or minutes on repositories where the older `git filter-branch` would run for hours. ## Selecting paths Path arguments are **inclusive** by default: naming a path means "the rewritten history contains only this". - `--path <path>` matches a file or a directory prefix. - `--path-glob '<pattern>'` matches a shell-style glob. - `--path-regex '<regex>'` matches a regular expression. All may be repeated and mixed; the selection is their union. Adding `--invert-paths` inverts the whole selection into "everything except these". So: - extract a subdirectory into its own project: `git filter-repo --subdirectory-filter services/billing` - purge a leaked file: `git filter-repo --path config/prod.env --invert-paths` - purge a whole class of files: `git filter-repo --path-glob '*.pfx' --invert-paths` Related filters worth knowing: `--path-rename old:new` moves a path across all of history, and `--subdirectory-filter <dir>` promotes a subdirectory to the repository root. ## The fresh-clone rule By default the tool aborts unless it is running in a freshly cloned repository. This is deliberate: the rewrite is not undoable, and a fresh clone means the original still exists somewhere untouched. `--force` overrides the check, and you should reach for it only when you personally know an intact copy exists. The recommended shape is therefore: clone, rewrite in the clone, inspect the result, then push. ## What it does after filtering Without being asked, `git filter-repo`: - rewrites **all** refs — branches, tags and notes — not just the current branch; - drops commits that became empty because their only changes touched removed paths; - writes `.git/filter-repo/commit-map` and `.git/filter-repo/ref-map`, mapping every old SHA to its replacement (or to zeros when the commit was dropped); - expires reflogs and garbage-collects, so the old objects are genuinely gone from that copy; - removes the `origin` remote, forcing a deliberate `git remote add` before anything can be pushed. That last behaviour surprises people and is the point: publishing a rewritten history should be a decision, not an accident. ## The consequence you must state out loud Rewriting the *first* affected commit changes its SHA, which changes its children's SHAs, and so on to every tip. In practice the entire history from the earliest touched commit forward is new. Consequences worth naming: - Every existing clone is now incompatible; a plain `git pull` will try to merge two parallel histories. - Anything that stored a commit SHA — release notes, deployment records, issue references, submodule pointers — now names a commit that exists only in old copies. - Signed commits lose their signatures, because a signature covers the old commit content. - The push must be forced and must cover every rewritten ref, not just the branch you were looking at. ## Verifying before you push Cheap checks that catch mistakes: `git log --all --oneline -- <removed-path>` should output nothing; `git count-objects -vH` should show the expected size drop; `git log --oneline | wc -l` shows how many commits were pruned as empty. Running `git filter-repo --analyze` *before* filtering writes size and path reports under `.git/filter-repo/analysis/`, so you can choose the right paths in the first place. ## When not to use it If the unwanted content exists only in commits that were never published, an ordinary local fixup is cheaper and disturbs nobody. Reach for a bulk rewrite when the content is spread across published history and no cheaper edit reaches it.
- Why does git filter-repo remove the origin remote when it finishes?To make publishing a deliberate act. The rewritten history is incompatible with the remote's, so a reflexive `git push` would either be rejected or, if forced, publish a rewrite nobody was warned about. Removing the remote forces you to re-add it consciously.
- How do you map an old commit SHA to its replacement after the rewrite?Look it up in `.git/filter-repo/commit-map`, which lists every original SHA and the SHA it became; commits pruned as empty map to all zeros. That is how you fix references stored outside the repository, such as deployment records.
- What happens to commits whose only content was in the removed path?They become empty and are pruned by default, so the rewritten history is shorter. A commit that still carries other changes survives with a new SHA and a smaller diff.
saying these in an interview costs you the question
- Thinks --path alone deletes the named path
- Runs the rewrite in the only copy of the repo
- Assumes only the current branch is rewritten
- Expects existing commit SHAs to stay valid
- Believes a normal push will publish the rewrite