git-filter-repo

repository·main·Indexed 11 days ago

https://github.com/newren/git-filter-repo

A high-performance tool for rewriting Git repository history, recommended by the Git project as a faster and safer alternative to git filter-branch and BFG Repo Cleaner. It can be used as a CLI tool or as a Python library to build custom history rewriting tools. Requires git >= 2.36.0 and python3 >= 3.6.

Tokens
10.3K
Snippets
44
Records
63
Agent score
95%

What's inside git-filter-repo

  1. Understand the differences between BFG Repo Cleaner and git filter-repo

    main

    When migrating from BFG Repo Cleaner to git filter-repo, note these fundamental architectural and behavioral differences:

    • Path Awareness: BFG operates on tree objects and only understands basenames (e.g., it cannot distinguish between README.md at the root vs. in a subdirectory). git filter-repo operates on the fast-export stream, meaning it works with full paths from the repository toplevel.
    • Scope of Changes: git filter-repo applies filters to HEAD by default to ensure your current working state matches the transformed history. BFG often requires manual synchronization.
    • Commit Metadata: git filter-repo does not add [formerly OLDHASH] or Former-commit-id: footers to commit messages. Instead, it uses replace refs, which provide a cleaner way to look up commits by their original hashes.
    • Cleanup: git filter-repo automatically handles updating the index and working tree, and runs an automatic gc (garbage collection) after the rewrite.
    • Execution Context: BFG expects the repository path as the final argument. git filter-repo expects you to cd into the repository directory before running the command.
  2. Compare git-filter-repo and git rebase for history manipulation

    main

    Choosing between git-filter-repo and git rebase depends on whether you want to manipulate the state of files or the changes (diffs) between commits.

    Featuregit-filter-repogit rebase
    Core MechanismUses fast-export/fast-import (snapshot-based)Operates on diffs (patch-based)
    Best Use CaseRemoving files entirely or mass-changing file contents across all historyRemoving specific commits or tweaking specific changes between commits
    BehaviorIf you tweak a file in one commit, subsequent commits that mention that file will revert your change unless you apply the tweak to every single commit touching that fileAllows you to drop or modify a diff; future diffs are then re-applied on top of the new state
    RiskHigh efficiency for structural changesRisk of merge conflicts when re-applying patches
  3. Configure git-filter-repo for use as a Python library

    main

    To use git-filter-repo as a Python library or to use the demonstration scripts in contrib/filter-repo-demos/, you must ensure a file named git_filter_repo.py is available in your $PYTHONPATH.

    This file should be a copy of or a symlink to the main git-filter-repo script. You can place it in your Python site-packages directory.

  4. Why `git-filter-repo` rewrites commit hashes

    main

    In Git, a commit hash is a cryptographic hash of its entire content, including the commit message, author, committer, the top-level tree hash, and the hashes of its parent(s).

    If git-filter-repo modifies any part of a commit (such as removing a file or changing a path), the commit's tree hash changes. Because the tree hash changes, the commit hash changes. Furthermore, because the parent hash is part of the next commit's content, any change to a commit causes a chain reaction that changes the hashes of all subsequent commits in the history.

  5. Use git-filter-repo as a library to build custom tools

    main

    Beyond its CLI capabilities, git-filter-repo functions as a library that allows you to write custom history rewriting tools. When building custom tools, your scripts will automatically inherit core functionality such as:

    • Rewriting hashes in commit messages
    • Pruning commits that become empty
    • Handling filenames with special characters or non-standard encodings
    • Handling of replace refs
  6. Safety mechanism: Fresh clone requirement

    main

    To prevent accidental data loss, git-filter-repo will detect if you are working in a fresh clone. If the repository is not a fresh clone, the tool will bail out to encourage a safer workflow (where you can simply delete the clone if an error occurs).

    To bypass this safety check and run the tool on an existing repository, use the --force flag.

    git filter-repo --force [options]
  7. Use multi-line strings in callbacks with `textwrap.dedent`

    main

    When writing multi-line strings inside a git filter-repo callback, Python's indentation can cause unwanted leading spaces to be included in the data. To prevent this, use textwrap.dedent() within your callback string.

    git filter-repo --blob-callback '
      import textwrap
      blob.data = bytes(textwrap.dedent("""
        This is the new
        file content.
        """), "utf-8")
    '
  8. Compare git-filter-repo with alternatives

    main

    The Git project recommends git-filter-repo over git filter-branch.

    vs filter-branch

    • Performance: filter-branch is significantly slower for non-trivial repositories.
    • Safety: filter-branch has known issues that can lead to silent corruption or messy rewrites.
    • Complexity: filter-branch is difficult to use for non-trivial rewrites.

    vs BFG Repo Cleaner

    • Capability: BFG is limited to specific types of rewrites, whereas git-filter-repo is more versatile.
    • Architecture: BFG's architecture is less amenable to handling diverse rewrite types and has known shortcomings/bugs.
  9. Why more commit hashes changed than expected

    main

    If you observe more hash changes than anticipated, it is due to one of two reasons:

    1. Downstream changes: Modifying an old commit changes its hash, which forces all descendant commits to also change their hashes to maintain the integrity of the parent-child chain.
    2. Canonicalization: git-filter-repo uses git-fast-export and git-fast-import, which canonicalize history. Even without a specific filter, hashes may change if:
      • Commit signatures are stripped.
      • Extended headers are stripped.
      • Non-UTF-8 encodings are re-encoded to UTF-8.
      • Commits without an author are assigned one matching the committer.
      • Trees are re-sorted into a canonical order.

    To restrict the rewrite to only newer history, use the --refs argument to specify a range of history. Note: Using --refs to try and only rewrite older commits is ineffective; you must rewrite all the way to the branch tip for the changes to be meaningful and part of the branch history.

  10. Understand the limitations of git-filter-repo

    main

    Before using git-filter-repo, it is important to understand what it is NOT designed to do. It is a one-shot history rewriting tool, not a tool for ongoing development or diff-based manipulation.

    What git-filter-repo does NOT do:

    • Keep original commit IDs: Modifying commits or files fundamentally changes their IDs. If you only want to avoid downloading everything without changing history, use partial clones or shallow clones instead.
    • Support bidirectional development: If you need to extract a subset of a repo, develop on it, and merge changes back and forth between the filtered and unfiltered versions, use Josh instead.
    • Operate on diffs (patches): git-filter-repo uses git fast-export and git fast-import, which treat commits as snapshots of file states rather than diffs. If you want to remove specific commits or modify the changes (diffs) between commits, use git rebase.
    • Guarantee identical IDs across different environments: Running the same command on two different clones may result in different commit IDs due to differences in Git versions, git-filter-repo versions, or the presence of local-only commits/different clone states.
  11. Access git-filter-repo documentation and examples

    main

    You can learn how to use git-filter-repo through several resources:

    Manuals

    • User Manual: Available via HTML preview.
    • Built-in Help: Use the -h flag in the CLI.

    Cheat Sheets & Examples

  12. Handle repository corruption in commit objects

    main

    If git fsck --full identifies corrupt commit objects, you can fix them by creating a replacement object using git replace and then making the change permanent with git filter-repo --proceed.

    1. Export the corrupt commit to a file: git cat-file -p <commit_hash> > tmp.
    2. Edit the file to fix the corruption (e.g., fixing malformed author/committer lines).
    3. Create a new commit object from the fixed file: git hash-object -t commit -w tmp.
    4. Apply the replacement: git replace -f <corrupt_hash> <new_hash>.
    5. Make it permanent: git filter-repo --proceed.
    $ git cat-file -p 166f57b3fbe31257100361ecaf735f305b533b21 >tmp
    # ... edit tmp ...
    $ git replace -f 166f57b3fbe31257100361ecaf735f305b533b21 $(git hash-object -t commit -w tmp)
    $ rm tmp
    $ git filter-repo --proceed