123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- #!/usr/bin/env python3
- """Example of using filter-branch to rewrite commit history.
- This demonstrates how to use dulwich's filter-branch functionality to:
- - Change author/committer information
- - Modify commit messages
- - Apply custom filters
- The example shows both the high-level porcelain interface and the
- lower-level filter_branch module API.
- """
- import sys
- from dulwich import porcelain
- from dulwich.filter_branch import CommitFilter, filter_refs
- from dulwich.repo import Repo
- def example_change_author(repo_path):
- """Example: Change all commits to have a new author."""
- print("Changing author for all commits...")
-
- def new_author(old_author):
- # Change any commit by "Old Author" to "New Author"
- if b"Old Author" in old_author:
- return b"New Author <new@example.com>"
- return old_author
-
- result = porcelain.filter_branch(
- repo_path,
- "HEAD",
- filter_author=new_author
- )
-
- print(f"Rewrote {len(result)} commits")
- return result
- def example_prefix_messages(repo_path):
- """Example: Add a prefix to all commit messages."""
- print("Adding prefix to commit messages...")
-
- def add_prefix(message):
- return b"[PROJECT-123] " + message
-
- result = porcelain.filter_branch(
- repo_path,
- "HEAD",
- filter_message=add_prefix
- )
-
- print(f"Rewrote {len(result)} commits")
- return result
- def example_custom_filter(repo_path):
- """Example: Custom filter that changes multiple fields."""
- print("Applying custom filter...")
-
- def custom_filter(commit):
- # This filter:
- # - Standardizes author format
- # - Adds issue number to message if missing
- # - Updates committer to match author
-
- changes = {}
-
- # Standardize author format
- if b"<" not in commit.author:
- changes["author"] = commit.author + b" <unknown@example.com>"
-
- # Add issue number if missing
- if not commit.message.startswith(b"[") and not commit.message.startswith(b"Merge"):
- changes["message"] = b"[LEGACY] " + commit.message
-
- # Make committer match author
- if commit.author != commit.committer:
- changes["committer"] = commit.author
-
- return changes if changes else None
-
- result = porcelain.filter_branch(
- repo_path,
- "HEAD",
- filter_fn=custom_filter
- )
-
- print(f"Rewrote {len(result)} commits")
- return result
- def example_low_level_api(repo_path):
- """Example: Using the low-level filter_branch module API."""
- print("Using low-level filter_branch API...")
-
- with Repo(repo_path) as repo:
- # Create a custom filter
- def transform_message(msg):
- # Add timestamp and uppercase first line
- lines = msg.split(b'\n')
- if lines:
- lines[0] = lines[0].upper()
- return b'[TRANSFORMED] ' + b'\n'.join(lines)
-
- # Create the commit filter
- commit_filter = CommitFilter(
- repo.object_store,
- filter_message=transform_message,
- filter_author=lambda a: b"Transformed Author <transformed@example.com>"
- )
-
- # Filter the master branch
- result = filter_refs(
- repo.refs,
- repo.object_store,
- [b"refs/heads/master"],
- commit_filter,
- keep_original=True,
- force=False,
- )
-
- print(f"Rewrote {len(result)} commits using low-level API")
- return result
- def main():
- if len(sys.argv) < 2:
- print("Usage: filter_branch.py <repo_path> [example]")
- print("Examples: change_author, prefix_messages, custom_filter, low_level")
- sys.exit(1)
-
- repo_path = sys.argv[1]
- example = sys.argv[2] if len(sys.argv) > 2 else "change_author"
-
- examples = {
- "change_author": example_change_author,
- "prefix_messages": example_prefix_messages,
- "custom_filter": example_custom_filter,
- "low_level": example_low_level_api,
- }
-
- if example not in examples:
- print(f"Unknown example: {example}")
- print(f"Available examples: {', '.join(examples.keys())}")
- sys.exit(1)
-
- try:
- examples[example](repo_path)
- print("Filter-branch completed successfully!")
- except Exception as e:
- print(f"Error: {e}")
- sys.exit(1)
- if __name__ == "__main__":
- main()
|