Skip to main content
This cheat sheet provides quick reference for common git-filter-repo operations. Use the tabs below to navigate between different migration scenarios and common use cases.

Converting from filter-branch

filter-branch:
git filter-branch --tree-filter 'rm filename' HEAD
# or
git filter-branch --tree-filter 'rm -f filename' HEAD
# or (faster)
git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD
filter-repo:
git filter-repo --invert-paths --path filename

Converting from BFG Repo Cleaner

BFG:
java -jar bfg.jar --strip-blobs-bigger-than 100M some-big-repo.git
filter-repo:
git filter-repo --strip-blobs-bigger-than 100M

Common Operations

Purge a large list of files

Create a file with paths to delete (one per line):
git filter-repo --invert-paths --paths-from-file ../DELETED_FILENAMES.txt

Removing a directory

git filter-repo --path node_modules/electron/dist/ --invert-paths

Removing paths with a certain extension

git filter-repo --invert-paths --path-glob '*.xsa'
Or using a callback:
git filter-repo --filename-callback '
    if filename.endswith(b".xsa"):
        return None
    return filename'

Extract library from subdirectory

Keep src/some-folder/some-feature/ but rename it to src/:
git filter-repo \
    --path src/some-folder/some-feature/ \
    --path-rename src/some-folder/some-feature/:src/

Only keep files from specific branches

git ls-tree -r ${BRANCH1} >../my-files
git ls-tree -r ${BRANCH2} >>../my-files
sort ../my-files | uniq >../my-relevant-files
git filter-repo --paths-from-file ../my-relevant-files

Replace words in all commit messages

git filter-repo --message-callback 'return message.replace(b"stuff", b"task")'

Remove spaces at end of lines

git filter-repo --replace-text <(echo 'regex:[\r\t ]+(\n|$)==>\n')

Replace binary blob in history

Replace blob with hash f4ede2e944868b9a08401dafeb2b944c7166fd0a:
git filter-repo --blob-callback '
    if blob.original_id == b"f4ede2e944868b9a08401dafeb2b944c7166fd0a":
        blob.data = open("../alternative-file.jpg", "rb").read()
'
Or using git replace:
git replace -f f4ede2e944868b9a08401dafeb2b944c7166fd0a \
    $(git hash-object -w ../alternative-file.jpg)
git filter-repo --proceed

Renormalize line endings and add .gitattributes

contrib/filter-repo-demos/lint-history dos2unix
# Edit .gitattributes
contrib/filter-repo-demos/insert-beginning .gitattributes

Include and exclude rules

Include all files under src/ except src/README.md:
git filter-repo --filename-callback '
    if filename == b"src/README.md":
        return None
    if filename.startswith(b"src/"):
        return filename
  return None'

Remove files with backslashes

git filter-repo --filename-callback 'return None if b"\\" in filename else filename'

Convert NFD filenames to NFC

git filter-repo --filename-callback '
    import unicodedata
    try:
       return bytearray(unicodedata.normalize("NFC", filename.decode("utf-8")), "utf-8")
    except:
      return filename
'

Set committer of last few commits

git filter-repo --refs main~5..main --commit-callback '
    commit.committer_name = b"My Wonderful Self"
    commit.committer_email = b"[email protected]"
'

Handle special characters in names

git filter-repo --refs main~5..main --commit-callback '
    if commit.author_email == b"[email protected]":
        commit.author_name = "Raphaël González".encode()
        commit.author_email = b"[email protected]"
'

Add files to root commits

git filter-repo --commit-callback "if not commit.parents: commit.file_changes += [
    FileChange(b'M', b'README.md', b'$(git hash-object -w '/path/to/README.md')', b'100644'), 
    FileChange(b'M', b'src/.gitignore', b'$(git hash-object -w '/path/to/.gitignore')', b'100644')]"

Update submodule hashes

Update submodule at src/my-submodule according to mapping:
git filter-repo --file-info-callback '
    if filename == b"src/my-submodule" and blob_id == b"edf570fde099c0705432a389b96cb86489beda09":
        blob_id = b"9cce52ae0806d695956dcf662cd74b497eaa7b12"
    if filename == b"src/my-submodule" and blob_id == b"644f7c55e1a88a29779dc86b9ff92f512bf9bc11":
        blob_id = b"88b02e9e45c0a62db2f1751b6c065b0c2e538820"
    return (filename, mode, blob_id)
'

Replace PNGs with compressed versions

First identify the blob IDs:
git log -1 --raw --no-abbrev ${COMMIT_WHERE_YOU_COMPRESSED_PNGS}
Then replace:
git filter-repo --file-info-callback '
    if filename == b"resources/foo.png" and blob_id == b"edf570fde099c0705432a389b96cb86489beda09":
        blob_id = b"9cce52ae0806d695956dcf662cd74b497eaa7b12"
    if filename == b"resources/bar.png" and blob_id == b"644f7c55e1a88a29779dc86b9ff92f512bf9bc11":
        blob_id = b"88b02e9e45c0a62db2f1751b6c065b0c2e538820"
    return (filename, mode, blob_id)
'

Remove commits older than N days

This changes every commit hash and discards history. Use with caution.
git replace --graft ${OLD_COMMIT}
git filter-repo --proceed
The git replace --graft command with no parent arguments makes ${OLD_COMMIT} a new root commit.

Restricting to a Range

All filter-repo commands can be restricted to a specific range of commits:
# Single range
git filter-repo ... --refs C..H

# With exclusions
git filter-repo ... --refs C..H ^D

# Alternative range
git filter-repo ... --refs D..H ^C
Use ^C instead of --not C since --not appears to be a flag name which breaks parsing.

Build docs developers (and LLMs) love