Skip to main content

Your First Filtering Operation

This guide walks you through a complete example of using git-filter-repo to extract a subdirectory from a repository and prepare it for merging into another project.
git-filter-repo is designed to work on a fresh clone for safety. It will refuse to run on a repository with uncommitted changes or unpushed commits unless you use --force.

Example Scenario

Let’s say you want to:
  • Extract only the src/ directory from a repository
  • Move everything into a new my-module/ subdirectory
  • Rename all tags with a my-module- prefix to avoid conflicts
This is a common workflow when splitting a monorepo or preparing code to merge into another project.
1

Create a fresh clone

Start with a fresh clone of your repository:
git clone https://github.com/yourname/yourrepo.git repo-to-filter
cd repo-to-filter
Always work on a fresh clone! git-filter-repo rewrites history and removes the remote by default to prevent accidental pushes.
2

Analyze your repository (optional)

Before filtering, you can analyze your repository to understand its structure:
git filter-repo --analyze
This creates a .git/filter-repo/analysis/ directory containing reports about:
  • Largest files
  • Most commits per path
  • File and directory sizes
  • Extensions used
cat .git/filter-repo/analysis/path-all-sizes.txt
3

Run the filter operation

Now perform the actual filtering with a single command:
git filter-repo --path src/ --to-subdirectory-filter my-module --tag-rename '':'my-module-'
Let’s break down what each option does:
--path src/
string
Keep only files under the src/ directory. Everything else is removed.
--to-subdirectory-filter my-module
string
Move all remaining files into a new my-module/ subdirectory. So src/foo.c becomes my-module/src/foo.c.
--tag-rename '':'my-module-'
string
Rename all tags by prepending my-module- to prevent naming conflicts. The empty string before the colon is the old prefix to replace.
4

Verify the results

Check that the filtering worked as expected:
# View the file structure
ls -la

# Check git history
git log --oneline --graph --all

# Verify only src/ files remain (now in my-module/src/)
git ls-tree -r --name-only HEAD

# Check renamed tags
git tag
Notice that commits which only touched files outside src/ have been completely removed from the history.
5

Push to a new remote (optional)

If you want to push your filtered repository:
# Add new remote (filter-repo removes the old one)
git remote add origin https://github.com/yourname/filtered-repo.git

# Push all branches and tags
git push --all origin
git push --tags origin

How Fast Is It?

One of git-filter-repo’s key advantages is speed. For reference:

filter-repo

Minutes for large repos

filter-branch

Hours to days for the same repos

Common Quick Operations

Here are more examples you can try:
Remove a sensitive file that was accidentally committed:
git filter-repo --invert-paths --path secrets.txt
The --invert-paths flag means “remove” instead of “keep”.
Make a subdirectory the root of the repository:
git filter-repo --subdirectory-filter path/to/subdir
Everything in path/to/subdir becomes the root, and everything else is removed.
Remove all .log files from history:
git filter-repo --invert-paths --path-glob '*.log'
Replace text across all files in history:
git filter-repo --replace-text <(echo 'old-text==>new-text')
Or use a file with multiple replacements:
# Create replacements.txt:
# literal:password123==>***REMOVED***
# regex:API_KEY_[A-Z0-9]+==>[REDACTED]

git filter-repo --replace-text replacements.txt
First analyze to find large files:
git filter-repo --analyze
cat .git/filter-repo/analysis/blob-shas-and-paths.txt | head -20
Then remove them:
git filter-repo --strip-blobs-bigger-than 10M

Command Comparison

Coming from other tools? Here’s how git-filter-repo compares:
git filter-repo --path src/ \
  --to-subdirectory-filter my-module \
  --tag-rename '':'my-module-'
1 simple command, fast, and handles all edge cases correctly.

Safety Features

git-filter-repo includes several safety features:

Fresh Clone Check

Refuses to run unless in a fresh clone (override with --force)

Remote Removal

Removes git remotes by default to prevent accidental pushes of rewritten history

Empty Commit Pruning

Automatically removes commits that become empty after filtering

Auto Cleanup

Automatically runs garbage collection to remove old objects

What Happened to My Repo?

After running git-filter-repo:
1

History is rewritten

All commit hashes change because the repository history has been rewritten. Old commit references are invalid.
2

Remotes are removed

The origin remote is removed by default. This prevents accidentally pushing rewritten history.Add it back if needed:
git remote add origin <url>
3

Objects are cleaned

Old objects and refs are automatically cleaned up with git gc.
4

Commits may be removed

Commits that:
  • Only touched filtered-out paths
  • Become empty after filtering
  • Become duplicate merges
…are automatically removed from history.

Next Steps

Common Use Cases

Explore more filtering scenarios

Path-Based Filtering

Learn advanced path filtering techniques

Command Reference

See all available options

Migration Guides

Migrate from filter-branch or BFG

Getting Help

If you run into issues:
# Quick help
git filter-repo -h

# Full manual (if installed)
git filter-repo --help

# Or visit the FAQ

FAQ & Troubleshooting

Find answers to common questions

Build docs developers (and LLMs) love