{"id":5769,"library":"git-filter-repo","title":"Git Filter Repo","description":"git-filter-repo is a powerful and fast tool for rewriting Git repository history, designed as a modern and more efficient replacement for the deprecated `git filter-branch`. It is currently at version 2.47.0 and receives active development, with releases typically occurring every few weeks to months, addressing common repository maintenance tasks like removing sensitive data, extracting subdirectories, and reorganizing history.","status":"active","version":"2.47.0","language":"en","source_language":"en","source_url":"https://github.com/newren/git-filter-repo","tags":["git","version control","history rewrite","repository management","cli"],"install":[{"cmd":"pip install git-filter-repo","lang":"bash","label":"PyPI"}],"dependencies":[{"reason":"git-filter-repo interacts directly with git repositories and requires the 'git' executable (version >= 2.36.0) to be available in the system PATH.","package":"git","optional":false}],"imports":[{"note":"git-filter-repo is primarily designed as a command-line utility. While it can be imported as a Python library for custom filtering scripts, its internal API is not guaranteed to be stable between versions. Most common usage from Python involves invoking the command-line tool via `subprocess`.","wrong":"from git_filter_repo import SomeClassOrFunction","symbol":"git-filter-repo (CLI) / git_filter_repo (Library)","correct":"For command-line execution: `subprocess.run(['git-filter-repo', '--option', 'value'])`. For programmatic library use (advanced): `import git_filter_repo as fr` (requires specific setup, see notes)."}],"quickstart":{"code":"import subprocess\nimport os\nimport shutil\n\n# --- Setup: Create a dummy repo for demonstration ---\nrepo_name = \"test_repo_filter\"\nrepo_path = os.path.join(os.getcwd(), repo_name)\n\n# Clean up previous run if exists\nif os.path.exists(repo_path):\n    shutil.rmtree(repo_path)\n\nos.makedirs(repo_path)\nos.chdir(repo_path)\n\nsubprocess.run([\"git\", \"init\", \"-b\", \"main\"], check=True, capture_output=True)\nsubprocess.run([\"git\", \"config\", \"user.email\", \"test@example.com\"], check=True, capture_output=True)\nsubprocess.run([\"git\", \"config\", \"user.name\", \"Test User\"], check=True, capture_output=True)\n\nwith open(\"file1.txt\", \"w\") as f:\n    f.write(\"initial content\")\nsubprocess.run([\"git\", \"add\", \"file1.txt\"], check=True, capture_output=True)\nsubprocess.run([\"git\", \"commit\", \"-m\", \"Initial commit\"], check=True, capture_output=True)\n\nwith open(\"secret.txt\", \"w\") as f:\n    f.write(\"super secret info\")\nsubprocess.run([\"git\", \"add\", \"secret.txt\"], check=True, capture_output=True)\nsubprocess.run([\"git\", \"commit\", \"-m\", \"Add secret file\"], check=True, capture_output=True)\n\nwith open(\"file1.txt\", \"a\") as f:\n    f.write(\"\\nmore content\")\nsubprocess.run([\"git\", \"add\", \"file1.txt\"], check=True, capture_output=True)\nsubprocess.run([\"git\", \"commit\", \"-m\", \"Update file1\"], check=True, capture_output=True)\n\nprint(\"Original log (last 3 commits):\")\nsubprocess.run([\"git\", \"log\", \"--oneline\", \"-3\"], check=True)\n\nprint(\"\\n--- Running git-filter-repo to remove 'secret.txt' ---\")\n# IMPORTANT: git-filter-repo *modifies history irreversibly*. Always back up your repository.\n# For this demo, we run directly. In a real scenario, consider cloning a backup first.\n\ntry:\n    # Ensure a clean working directory, which git-filter-repo often requires.\n    subprocess.run([\"git\", \"reset\", \"--hard\"], check=True, capture_output=True)\n    \n    # The actual filter-repo command to remove 'secret.txt' from all history.\n    # '--force' is often needed to bypass safety checks in non-fresh clones or testing.\n    filter_repo_cmd = [\"git-filter-repo\", \"--path-rename\", \"secret.txt:--delete\", \"--force\"]\n    print(f\"Executing: {' '.join(filter_repo_cmd)}\")\n    subprocess.run(filter_repo_cmd, check=True)\n\n    print(\"\\nFiltered log (last 3 commits):\")\n    subprocess.run([\"git\", \"log\", \"--oneline\", \"-3\"], check=True)\n\n    # Verify the file is gone and not in history\n    search_log_cmd = [\"git\", \"log\", \"--all\", \"--\", \"secret.txt\"]\n    result = subprocess.run(search_log_cmd, capture_output=True, text=True)\n    if not result.stdout:\n        print(\"\\n'secret.txt' successfully removed from history.\")\n    else:\n        print(\"\\nERROR: 'secret.txt' still found in history. Output:\\n\" + result.stdout)\n\nexcept subprocess.CalledProcessError as e:\n    print(f\"Error running git-filter-repo: {e}\")\n    print(f\"Stdout: {e.stdout.decode()}\")\n    print(f\"Stderr: {e.stderr.decode()}\")\n\nfinally:\n    # Clean up the dummy repo\n    os.chdir(\"../\")\n    if os.path.exists(repo_path):\n        shutil.rmtree(repo_path)\n","lang":"python","description":"This quickstart demonstrates how to use `git-filter-repo` via `subprocess` to remove a specific file (`secret.txt`) from a repository's entire history. It sets up a temporary Git repository, adds a 'secret' file, commits it, then uses `git-filter-repo --path-rename secret.txt:--delete --force` to eradicate its presence. Note that `git-filter-repo` modifies history irreversibly, so always back up your repository before use in production."},"warnings":[{"fix":"Rewrite scripts and commands to use `git-filter-repo`'s new syntax and paradigms. Consult the `git-filter-repo` documentation carefully for migration guides.","message":"git-filter-repo is the official replacement for `git filter-branch`, which is now deprecated by the Git project itself. Its command-line arguments and internal behavior are significantly different. Existing scripts or workflows relying on `git filter-branch` will break if migrated without modification.","severity":"breaking","affected_versions":"All versions (since its inception as a replacement)"},{"fix":"Always create a fresh clone or a full backup of your repository (e.g., `git clone --mirror`) before running `git-filter-repo`. Inform all collaborators about history rewrites and instruct them to rebase or re-clone their repositories.","message":"git-filter-repo irreversibly rewrites your Git repository's history across all branches and tags. This can lead to data loss or desynchronization issues for collaborators if not handled correctly. Never run it on a production repository without a full backup, and communicate changes clearly to your team.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure your working directory is clean (`git status` should show nothing to commit or stage), push all relevant branches, or run `git reset --hard` and `git clean -fdx` before executing. If working with a clone, ensure it's a full clone (`git clone --mirror` is often recommended for safety).","message":"git-filter-repo requires a 'pristine' repository state: no uncommitted changes, no unpushed commits on the current branch (unless `--force` is used carefully), and a full (non-shallow) clone. It will often refuse to run if these conditions are not met, to prevent accidental data loss.","severity":"gotcha","affected_versions":"All versions"},{"fix":"If using it as a library, pin to a specific `git-filter-repo` version and test thoroughly after any upgrades. Consider contributing test cases for APIs you rely on. For general-purpose use from Python, execute it as a separate process using `subprocess.run()` or similar.","message":"While `git-filter-repo` is written in Python and can be used programmatically as a library (e.g., `import git_filter_repo`), its API is explicitly NOT guaranteed to be stable and may change between versions. Most users interact with it as a command-line tool.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-14T00:00:00.000Z","next_check":"2026-07-13T00:00:00.000Z"}