Skip to main content

git-rewrite

Bulk-rewrite any part of git commit history: messages, author names, emails, and more.

Why

Tools like git-filter-repo and git filter-branch are incredibly powerful — they give you full control over every commit object in your history. git-rewrite draws inspiration from both and builds on top of them to make common rewrite tasks safer and more approachable:

  • preview first — see exactly which commits match before changing anything
  • dry-run — validate the full command without rewriting a single commit
  • safe pattern embedding — patterns are repr-encoded so backslashes and quotes can't break the generated code
  • callback validation — custom scripts are syntax-checked and inspected for process_commit before history is touched
  • escape hatch — when regex isn't enough, drop down to a plain Python file and get full access to every commit field

Under the hood it uses git-filter-repo when available, falling back to git filter-branch.

Installation

pip install -e /path/to/git-rewrite

This installs the git-rewrite console script.

Shell completions

Tab-completion for subcommands, --field choices, and --refs (populated from git branch -a) via argcomplete:

pip install 'git-rewrite[completions]'

# bash (add to ~/.bashrc)
eval "$(register-python-argcomplete git-rewrite)"

# zsh (add to ~/.zshrc)
autoload -U bashcompinit && bashcompinit
eval "$(register-python-argcomplete git-rewrite)"

# fish (add to ~/.config/fish/config.fish)
register-python-argcomplete --shell fish git-rewrite | source

Usage

git-rewrite <command> [options]

Commands

preview — find matching commits (read-only)

git-rewrite preview "Co-Authored-By: Claude"
git-rewrite preview "Co-Authored-By: Claude" --limit 50
git-rewrite preview "Co-Authored-By: Claude" --refs main

# Machine-readable NDJSON output (one object per match, pipe to jq)
git-rewrite preview "Co-Authored-By: Claude" --format json
git-rewrite preview "Co-Authored-By: Claude" --format json | jq '.sha'

strip — remove matching lines from commit messages

# Diff-style dry-run: see exactly what would change before rewriting
git-rewrite strip "Co-Authored-By: Claude.*<noreply@anthropic\.com>" --preview

# Dry run first
git-rewrite strip --dry-run "Co-Authored-By: Claude.*<noreply@anthropic\.com>"

# Remove the lines
git-rewrite strip "Co-Authored-By: Claude.*<noreply@anthropic\.com>"

# Target a different field (requires git-filter-repo)
git-rewrite strip --field author-email "old@example\.com"

# Keep only conventional-trailer lines, strip the rest of the body
git-rewrite strip --invert "^[A-Z][a-z-]+: " --field message

Note: Using strip on a date field (--field author-date or --field committer-date) zeroes the field to an empty byte string, producing an invalid date. Use replace instead to rewrite specific parts of the date value while keeping it valid.

replace — substitute a pattern with a replacement

# Diff-style dry-run: see before/after before rewriting
git-rewrite replace "Co-Authored-By: Claude Sonnet \d+\.\d+" "Co-Authored-By: AI" --preview

git-rewrite replace "Co-Authored-By: Claude Sonnet \d+\.\d+" "Co-Authored-By: AI"
git-rewrite replace --field author-name "Old Name" "New Name"

# Normalize timezone offsets to UTC (requires git-filter-repo)
# Date values are in raw format: "<unix-timestamp> <tz-offset>", e.g. "1700000000 -0700"
git-rewrite replace --field author-date "[-+]\d{4}$" "+0000"
git-rewrite replace --field committer-date "[-+]\d{4}$" "+0000"

run — execute a custom Python callback

git-rewrite run my_callback.py --dry-run
git-rewrite run my_callback.py --refs main feature/branch

preset — run a named preset from the repo config

# Run the preset as-is
git-rewrite preset strip-ai

# Dry-run a preset
git-rewrite preset strip-ai --dry-run

# Override a flag from the CLI (CLI always wins over preset)
git-rewrite preset strip-ai --refs main --yes

Common flags

Flag Description
--dry-run Show what would happen without modifying history
--yes / -y Skip the confirmation prompt
--refs REF … Limit to specific refs (default: all)
--field FIELD Field to target: message, author-name, author-email, committer-name, committer-email, author-date, committer-date (date fields require git-filter-repo)
--case-sensitive Disable case-insensitive matching
--preview (strip/replace) Diff-style preview of changes — no history rewritten
--invert (strip) Keep only matches; strip everything else
--format FORMAT (preview) text (default) or json (NDJSON, one line per match)
--no-color Disable colored output (also honored via NO_COLOR env var)

Scoping flags (strip, replace, preview)

These flags narrow which commits are previewed and counted. DATE accepts any format git log understands (2024-01-01, 6 months ago, yesterday, etc.).

Note: Scoping flags filter which commits are shown/counted, not which commits the rewrite callback runs against. The actual rewrite still processes every commit in the given refs.

Flag Description
--since DATE Only consider commits more recent than DATE
--until DATE Only consider commits older than DATE
--author PATTERN Only consider commits whose author name/email matches PATTERN (regex)
# Preview only Claude co-authorship lines from the last 6 months
git-rewrite preview "Co-Authored-By: Claude" --since "6 months ago"

# Count matches by a specific contributor
git-rewrite strip --dry-run "Co-Authored-By: Claude" --author "alice@example.com"

# Scope by both date range and author
git-rewrite replace "OldOrg" "NewOrg" --since 2024-01-01 --until 2025-01-01 --author "dev@oldorg.com"

Custom callbacks (run)

Create a .py file defining process_commit:

import re

def process_commit(commit):
    """Receives a git-filter-repo commit object. Modify in place."""
    commit.message = re.sub(rb"Co-Authored-By: Claude.*\n?", b"", commit.message)
    # Also available:
    #   commit.author_name, commit.author_email
    #   commit.committer_name, commit.committer_email
    #   commit.author_date, commit.committer_date

The tool validates syntax and the presence of process_commit before touching history.

Config file

Store default options and named presets in a per-repository config file so teams can run the same cleanup repeatedly without repeating flags.

Config locations (checked in order)

  1. .git-rewrite.toml in the repo root
  2. [tool.git-rewrite] section in pyproject.toml

Schema

# .git-rewrite.toml

# Top-level defaults — applied to every command unless overridden on the CLI
default_refs = ["main", "develop"]
case_sensitive = false

# Named presets — run with: git-rewrite preset <name>
[presets.strip-ai]
command = "strip"
pattern = "Co-Authored-By:.*Claude.*"
field = "message"

[presets.fix-email]
command = "replace"
pattern = "old@example\\.com"
replacement = "new@example.com"
field = "author-email"

The same schema works inside pyproject.toml under [tool.git-rewrite]:

[tool.git-rewrite]
default_refs = ["main"]

[tool.git-rewrite.presets.strip-ai]
command = "strip"
pattern = "Co-Authored-By:.*Claude.*"

Precedence

CLI flag → preset value → top-level config default → built-in default

Note on case_sensitive: --case-sensitive is a boolean flag with no inverse (--case-insensitive). If you set case_sensitive = true in the config and need to override it to false for a single run, edit the config temporarily or omit the key.

Config parse errors

A malformed TOML file or an unknown preset name exits immediately with a clear error message listing what went wrong and (for unknown presets) the available preset names.

After rewriting

Force-push the affected refs:

git push --force-with-lease --all

Requirements

  • Python 3.10+
  • git
  • git-filter-repo (recommended; pip install git-filter-repo or brew install git-filter-repo)
    • Required for --dry-run and non-message fields
    • Falls back to git filter-branch for message-only rewrites

Release files for git-rewrite-history 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for git-rewrite-history 0.1.3
File Size Uploaded
git_rewrite_history-0.1.3.tar.gz 29.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for git-rewrite-history 0.1.3
File Interpreter ABI Platform
git_rewrite_history-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 48.0 kB

Release files / git_rewrite_history-0.1.3.tar.gz

Download URL git_rewrite_history-0.1.3.tar.gz
Size 29.2 kB
Tags Source
SHA-256 checksum
How to use checksums
ab6edd28599a603c8800b5ec8734a5425ce4d7d27696753664f98f42188603cb
BLAKE2b-256 checksum
How to use checksums
a685b4ed093287269282ec30c5108d6f3b2623c6ab4af3f154471d5c8935430e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / git_rewrite_history-0.1.3-py3-none-any.whl

Download URL git_rewrite_history-0.1.3-py3-none-any.whl
Size 18.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2bf79cf9c631de07873734a4d6b1a1b214e6dfa877ceb560b1b9441cfbedc0ff
BLAKE2b-256 checksum
How to use checksums
1b4d2fedb00d56e39436f927679a3015f5551dc90dfeec6958c1820a632ab1bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page