Skip to main content

Overview

Commit message rewriting allows you to fix typos, update references, add metadata, or standardize formatting across your entire repository history.

Replace Text in Messages

Basic Replacement

Replace text in commit and tag messages:
# Fix typo
teh==>the
recieve==>receive

# Update project name
old-project-name==>new-project-name

# Fix issue tracker references
bug#==>issue#
--replace-message uses the same syntax as --replace-text, but only applies to commit and tag messages, not file contents.

Replacement Syntax

Supports literal text, globs, and regex:
# Simple text replacement
foo==>bar
OldCo==>NewCo

Using Message Callback

Add Metadata

Add Signed-off-by or other metadata:
git filter-repo --message-callback '
  # Add Signed-off-by if missing
  if b"Signed-off-by:" not in message:
    message += b"\n\nSigned-off-by: John Doe <[email protected]>"
  return message
'

Fix Formatting

Standardize commit message format:
git filter-repo --message-callback '
  lines = message.split(b"\n")
  
  # Ensure first line is capitalized
  if lines and len(lines[0]) > 0:
    lines[0] = lines[0][0:1].upper() + lines[0][1:]
  
  # Remove trailing whitespace from all lines
  lines = [line.rstrip() for line in lines]
  
  # Ensure blank line between subject and body
  if len(lines) > 1 and lines[1] != b"":
    lines.insert(1, b"")
  
  return b"\n".join(lines)
'

Update Issue References

Convert between issue tracking systems:
git filter-repo --message-callback '
  # Convert #123 to JIRA-123
  message = re.sub(b"#(\\d+)", b"JIRA-\\1", message)
  
  # Convert "Fixes #123" to "Closes JIRA-123"
  message = re.sub(b"Fixes #(\\d+)", b"Closes JIRA-\\1", message)
  
  return message
'

Rewriting Commit Hashes

Automatic Hash Updates

By default, git-filter-repo updates commit hash references in messages:
# Before filtering:
"Reverts commit abc123def"

# After filtering (if abc123def was rewritten to xyz789abc):
"Reverts commit xyz789abc"
Automatic hash rewriting:
  • Preserves abbreviated hash length
  • Only updates hashes of commits being rewritten
  • Skips hashes that don’t exist or were pruned

Disable Hash Rewriting

Prevent automatic hash updates:
git filter-repo --preserve-commit-hashes \
  --replace-message expressions.txt
Disabling hash rewriting means commit references in messages will be incorrect after filtering.

Encoding Handling

Default Behavior

By default, git-filter-repo re-encodes commit messages to UTF-8:
# Commit with encoding=iso-8859-1 will be converted to UTF-8
git filter-repo ...

Preserve Original Encoding

Keep commit message encodings unchanged:
git filter-repo --preserve-commit-encoding \
  --replace-message expressions.txt
If a commit message isn’t valid in its specified encoding, git won’t re-encode it (it will leave both the message and encoding header unchanged).

Complex Examples

Standardize Commit Format

Enforce Conventional Commits format:
git filter-repo --message-callback '
  lines = message.split(b"\n")
  if not lines:
    return message
  
  subject = lines[0]
  
  # Add type if missing
  if not re.match(b"^(feat|fix|docs|style|refactor|test|chore):", subject):
    # Guess type based on content
    if b"test" in subject.lower():
      subject = b"test: " + subject
    elif b"doc" in subject.lower():
      subject = b"docs: " + subject
    elif b"fix" in subject.lower():
      subject = b"fix: " + subject
    else:
      subject = b"chore: " + subject
    
    lines[0] = subject
  
  return b"\n".join(lines)
'
Automatically link to issue tracker:
git filter-repo --message-callback '
  # Convert "Fixes #123" to "Fixes #123\nSee: https://github.com/user/repo/issues/123"
  def add_link(match):
    issue = match.group(1)
    url = b"https://github.com/user/repo/issues/" + issue
    return match.group(0) + b"\nSee: " + url
  
  message = re.sub(
    b"(Fixes|Closes|Resolves) #(\\d+)",
    add_link,
    message
  )
  
  return message
'

Remove Sensitive Information

Redact sensitive data from commit messages:
cat > redact.txt <<EOF
# Remove passwords
regex:password\s*[:=]\s*\S+==>[REDACTED]

# Remove API keys  
regex:api[_-]?key\s*[:=]\s*\S+==>[REDACTED]

# Remove emails
regex:[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}==>[EMAIL]
EOF

git filter-repo --replace-message redact.txt

Prefix All Commits

Add a prefix to all commit messages:
git filter-repo --message-callback '
  prefix = b"[LEGACY] "
  if not message.startswith(prefix):
    message = prefix + message
  return message
'

Clean Up Merge Messages

Standardize merge commit messages:
git filter-repo --commit-callback '
  # Only process merge commits
  if len(commit.parents) <= 1:
    return
  
  # Standardize merge message format
  lines = commit.message.split(b"\n")
  if lines[0].startswith(b"Merge "):
    # Extract branch name if possible
    match = re.search(b"Merge branch \'([^\']+)\'", lines[0])
    if match:
      branch = match.group(1)
      commit.message = b"Merge branch %s\n\nMerged %s into current branch." % (branch, branch)
'

Tag Messages

Modify Tag Messages

Tag messages are modified with --replace-message too:
git filter-repo --replace-message expressions.txt
Or use --tag-callback for more control:
git filter-repo --tag-callback '
  # Add release notes link
  tag.message += b"\n\nRelease notes: https://example.com/releases/" + tag.ref
'

Add Tag Metadata

git filter-repo --tag-callback '
  if tag.tagger_name:
    tag.message += b"\n\nTagged by %s <%s> on %s" % (
      tag.tagger_name,
      tag.tagger_email,
      tag.tagger_date
    )
'

Multi-Line Messages

Format Multi-Line Messages

Handle messages with multiple paragraphs:
git filter-repo --message-callback '
  lines = message.split(b"\n")
  
  # Capitalize first line
  if lines:
    lines[0] = lines[0].capitalize()
  
  # Wrap long lines in body (after blank line)
  result = []
  in_body = False
  
  for line in lines:
    if not line.strip():
      in_body = True
      result.append(line)
    elif in_body and len(line) > 72:
      # Simple word wrap
      words = line.split()
      current = b""
      for word in words:
        if len(current) + len(word) + 1 > 72:
          result.append(current)
          current = word
        else:
          current += b" " + word if current else word
      if current:
        result.append(current)
    else:
      result.append(line)
  
  return b"\n".join(result)
'

Common Patterns

Fix Common Typos

typos.txt
regex:\bteh\b==>the
regex:\brecieve\b==>receive
regex:\boccured\b==>occurred
regex:\bseperate\b==>separate
regex:\bdefinately\b==>definitely
git filter-repo --replace-message typos.txt

Update Project URLs

urls.txt
http://old-site.com==>https://new-site.com
github.com/olduser/repo==>github.com/newuser/repo
git filter-repo --replace-message urls.txt

Remove Debug Messages

git filter-repo --message-callback '
  # Remove lines starting with DEBUG:
  lines = message.split(b"\n")
  lines = [l for l in lines if not l.startswith(b"DEBUG:")]
  return b"\n".join(lines)
'

Add Co-authored-by

Add co-authors based on commit content:
git filter-repo --commit-callback '
  # Add co-author if commit touches certain directories
  touched_dirs = set()
  for change in commit.file_changes:
    if change.filename.startswith(b"frontend/"):
      touched_dirs.add(b"frontend")
    elif change.filename.startswith(b"backend/"):
      touched_dirs.add(b"backend")
  
  # Add co-authors
  if b"frontend" in touched_dirs:
    if b"Co-authored-by: Frontend Dev" not in commit.message:
      commit.message += b"\nCo-authored-by: Frontend Dev <[email protected]>"
  
  if b"backend" in touched_dirs:
    if b"Co-authored-by: Backend Dev" not in commit.message:
      commit.message += b"\nCo-authored-by: Backend Dev <[email protected]>"
'

Limitations

Hash Rewriting LimitationsCommit hashes in messages won’t be updated if:
  • The hash doesn’t exist in the repository
  • The commit was pruned during filtering
  • The abbreviated hash isn’t unique
  • You used --preserve-commit-hashes
Suboptimal cases are logged to .git/filter-repo/suboptimal-issues
Binary DataCommit messages are bytes, not strings. Always use:
  • b"text" for literals
  • .split(b"\n") for line splitting
  • b"".join() for joining

Testing Changes

Preview with —dry-run

git filter-repo --dry-run \
  --replace-message expressions.txt
This saves filtered output to files for inspection without modifying the repository.

Test on Single Branch

git filter-repo --refs test-branch \
  --message-callback '...'

Verify Results

Check commit messages after filtering:
# View all commit messages
git log --all --oneline

# Search for specific text
git log --all --grep="search term"

# Check a specific commit
git show COMMIT_HASH

Best Practices

Testing Strategy
  1. Test on a small branch first
  2. Use --dry-run for complex operations
  3. Run --analyze before and after to compare
  4. Search for patterns you’re trying to remove
  5. Check edge cases (empty messages, very long messages)
Preserve HistoryBefore running:
# Make a backup branch
git branch backup-before-filter

# Or make a backup clone
git clone --no-local . ../backup-repo

Next Steps

Build docs developers (and LLMs) love