git-filter-repo Rewrite Git history

Version 2.47 · source

Start here

git clone <url> <dir>
Work in a fresh clone
git filter-repo --analyze
Report what bloats history
git filter-repo --path <path>
Keep only this path
git filter-repo --path <path> --invert-paths
Drop this path
git filter-repo --subdirectory-filter <dir>
Make a subdir the root
git filter-repo --replace-text <file>
Scrub secrets in content
git filter-repo --strip-blobs-bigger-than 10M
Remove huge files
git remote add origin <url>
Re-add the removed remote

Concepts

  1. Fresh clone throwaway copy
  2. Rewritten history new hashes, origin removed
  3. Check result log, ls-files
  4. New remote collaborators re-clone

Analyze

git filter-repo --analyze
Write size and path reports
Output goes to .git/filter-repo/analysis; nothing is modified.
git clone --no-local <src> <dst>
Fresh clone of a local repo
git filter-repo --force
Skip the fresh-clone check
Only when you have a backup; the check protects you.
git filter-repo --dry-run
Save before/after streams only
git filter-repo --quiet
Pass quiet to git commands
git filter-repo --debug
Print extra detail

Select paths

--path <path>
Keep a file or directory
--path <dir>/ --path <file>
Keep several paths (union)
--invert-paths
Remove the matched paths instead
--path-glob '<glob>'
Match paths by glob
--path-regex '<regex>'
Match paths by regex
--use-base-name
Match on file name only
--paths-from-file <file>
Read path rules from a file
--refs <ref>
Limit rewrite to some refs

Restructure

--subdirectory-filter <dir>
Extract a subdir as the root
--to-subdirectory-filter <dir>
Move the root into a subdir
--path-rename <old>:<new>
Rename a path or directory
Does not select paths; pair with --path if you want to filter too.
--tag-rename <old>:<new>
Rename tags by prefix

Content and messages

--replace-text <file>
Replace strings in file contents
File lines: literal, regex:<re> or glob:<g>, optionally followed by ==>text. Default replacement is ***REMOVED***.
--replace-message <file>
Replace strings in messages
--strip-blobs-bigger-than <size>
Drop files over a size
--strip-blobs-with-ids <file>
Drop blobs listed by ID
--mailmap <file>
Rewrite authors via mailmap
--use-mailmap
Use the .mailmap file
--sensitive-data-removal
Clean leaked data end to end
Fetches all refs first and can discard unpushed local changes.
--preserve-commit-hashes
Keep old hashes in messages

Pruning and callbacks

--prune-empty always|auto|never
Handle commits left empty
--prune-degenerate always|auto|never
Handle degenerate merges
--partial
Mix old and new history
Keeps origin and skips gc and reflog expiry.
--source <src> --target <dst>
Write result to another repo
--filename-callback '<python>'
Rewrite file names in Python
--message-callback '<python>'
Rewrite messages in Python
--name-callback '<python>'
Rewrite author names
--email-callback '<python>'
Rewrite author emails
--commit-callback '<python>'
Edit whole commit objects
--refname-callback '<python>'
Rename refs, tags included

After the rewrite

git remote add origin <url>
Restore the removed remote
git push --force --all
Publish rewritten branches
git push --force --tags
Publish rewritten tags
git log --oneline --stat
Check the result
git ls-files
Confirm remaining files

Recipes

Split a subdir into its own repo
git clone --no-local mono libcd libgit filter-repo --subdirectory-filter libs/core
Purge a file from all history
git clone <url> clean && cd cleangit filter-repo --path secrets.env --invert-pathsgit remote add origin <url>git push --force --all
Scrub a leaked token
# expressions.txtliteral:AKIAEXAMPLE123==>***REMOVED***regex:password=\w+==>password=***REMOVED***git filter-repo --replace-text expressions.txt
Shrink a bloated repo
git filter-repo --analyzeless .git/filter-repo/analysis/blob-shas-and-paths.txtgit filter-repo --strip-blobs-bigger-than 10M