Skip to main content

Pull request formatter and fixer with GitHub integration

Project description

๐Ÿ› ๏ธ Pull Request Fixer

A modern Python tool for automatically fixing pull request titles and bodies across GitHub organizations. Scans for blocked PRs and updates them based on commit messages.

Features

  • ๐Ÿ” Organization Scanning: Scan entire GitHub organizations for blocked pull requests
  • โœ๏ธ Title Fixing: Set PR titles to match the first commit's subject line
  • ๐Ÿ“ Body Fixing: Set PR descriptions to match commit message bodies (excluding trailers)
  • ๐Ÿ“„ File Fixing: Apply regex-based search/replace to files in PRs (clones, modifies, amends commit, and force-pushes changes)
  • ๐Ÿšซ Blocked PR Filtering: Option to process PRs that cannot merge (failing checks, conflicts, etc.)
  • ๐Ÿš€ Parallel Processing: Process PRs concurrently for performance
  • ๐Ÿ”„ Dry Run Mode: Preview changes before applying them
  • ๐Ÿ“Š Progress Tracking: Real-time progress updates during scanning
  • ๐ŸŽฏ Smart Parsing: Automatically removes Git trailers (Signed-off-by, etc.)
  • ๐Ÿ’ฌ PR Comments: Automatically adds a comment to PRs explaining the changes made

Installation

pip install pull-request-fixer

Or with uv:

uv pip install pull-request-fixer

Quick Start

# Set your GitHub token
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxx

# Fix PR titles in an organization
pull-request-fixer lfreleng-actions --fix-title

# Fix both titles and bodies
pull-request-fixer lfreleng-actions --fix-title --fix-body

# Show help (includes version)
pull-request-fixer --help

# Fix files in a specific PR using regex
pull-request-fixer https://github.com/owner/repo/pull/123 \
  --fix-files \
  --file-pattern './action.yaml' \
  --search-pattern '^\s+type:\s+\S' \
  --remove-lines \
  --context-start 'inputs:' \
  --context-end 'runs:' \
  --dry-run \
  --show-diff

# Preview changes without applying (dry run)
pull-request-fixer lfreleng-actions --fix-title --fix-body --dry-run

Usage

Basic Commands

Scan and fix an organization:

pull-request-fixer ORGANIZATION [OPTIONS]

Fix a specific PR:

pull-request-fixer PR_URL [OPTIONS]

You can specify the target as:

  • Organization name: myorg
  • GitHub URL: https://github.com/myorg
  • GitHub URL with path: https://github.com/myorg/
  • Specific PR URL: https://github.com/owner/repo/pull/123

Fix Options

--fix-title

Updates the PR title to match the first line (subject) of the first commit message.

Example:

If the first commit message is:

Fix authentication bug in login handler

This commit addresses an issue where users couldn't
log in with special characters in passwords.

Signed-off-by: John Doe <john@example.com>

This sets the PR title to:

Fix authentication bug in login handler

--fix-body

Updates the PR description to match the commit message body, excluding trailers.

Using the same commit message above, this sets the PR body to:

This commit addresses an issue where users couldn't
log in with special characters in passwords.

The Signed-off-by: trailer is automatically removed.

--fix-files

Fixes files in pull requests using regex-based search and replace. This feature:

  1. Clones the PR branch
  2. Finds files matching --file-pattern (regex)
  3. Applies search/replace using --search-pattern and --replacement
  4. Amends the last commit with the changes
  5. Force-pushes the updated commit back to the PR

Required options when using --fix-files:

  • --file-pattern: Regex to match file paths (e.g., './action.yaml' or '.*\.yaml$')
  • --search-pattern: Regex pattern to find in matched files
  • Either --replacement (text to replace matches) or --remove-lines (to delete matching lines)

Optional context options (for line removal):

  • --context-start: Regex to define where the removal context begins (e.g., 'inputs:')
  • --context-end: Regex to define where the removal context ends (e.g., 'runs:')

Optional display options:

  • --show-diff: Show unified diff output for file changes

Update Methods (for --fix-files only)

The tool supports two methods for applying file fixes:

API Method (default) - Uses GitHub API to create new commits:

pull-request-fixer https://github.com/owner/repo/pull/123 \
  --fix-files \
  --file-pattern './action.yaml' \
  --search-pattern 'pattern'
  • Creates new commits via GitHub API
  • Shows as "Verified" by GitHub
  • No Git operations required
  • Faster and simpler
  • Default method for ease of use

Git Method - Clones repo, amends commit, force-pushes:

pull-request-fixer https://github.com/owner/repo/pull/123 \
  --fix-files \
  --update-method git \
  --file-pattern './action.yaml' \
  --search-pattern 'pattern'
  • Respects your local Git signing configuration
  • Amends the existing commit (preserves commit history)
  • Requires Git operations (clone, amend, push)
  • Use when you need to amend commits or use your own signature

Example - Remove type definitions from GitHub Actions:

pull-request-fixer https://github.com/owner/repo/pull/123 \
  --fix-files \
  --file-pattern './action.yaml' \
  --search-pattern '^\s+type:\s+\S' \
  --remove-lines \
  --context-start 'inputs:' \
  --context-end 'runs:' \
  --dry-run \
  --show-diff

This will remove lines containing type: that appear between inputs: and runs: sections in action.yaml files.

Example - Replace text with regex:

pull-request-fixer https://github.com/owner/repo/pull/456 \
  --fix-files \
  --file-pattern '.*\.py$' \
  --search-pattern 'old_function_name' \
  --replacement 'new_function_name'

Common Usage Patterns

Fix titles:

pull-request-fixer myorg --fix-title

Fix both titles and bodies:

pull-request-fixer myorg --fix-title --fix-body

Preview changes (dry run):

pull-request-fixer myorg --fix-title --fix-body --dry-run

Include draft PRs:

pull-request-fixer myorg --fix-title --include-drafts

Use more workers for large organizations:

pull-request-fixer myorg --fix-title --workers 16

Quiet mode for automation:

pull-request-fixer myorg --fix-title --quiet

Verbose mode for debugging:

pull-request-fixer myorg --fix-files --file-pattern '*.yaml' \
  --search-pattern '^\s+type:\s+\S' --remove-lines \
  --verbose  # Shows detailed DEBUG logs including file operations

Process blocked PRs:

pull-request-fixer myorg --fix-title --blocked-only

Fix files across PRs:

pull-request-fixer myorg \
  --fix-files \
  --file-pattern './action.yaml' \
  --search-pattern '^\s+type:\s+\S' \
  --remove-lines \
  --context-start 'inputs:' \
  --context-end 'runs:' \
  --blocked-only \
  --dry-run

PR Comments

When the tool applies fixes (not in dry-run mode), it automatically adds a comment to the PR explaining the changes. This provides transparency and helps PR authors understand the automated modifications.

Example comment:

## ๐Ÿ› ๏ธ Pull Request Fixer

Automatically fixed pull request metadata:
- **Pull request title** updated to match first commit
- **Pull request body** updated to match commit message

---
*This fix was automatically applied by [pull-request-fixer](https://github.com/lfit/pull-request-fixer)*

The comment includes the items that changed. For example, if the title changed, that line will appear in the comment.

Options

Flag Short Default Description
--help -h Show help message and exit (displays version)
--token -t $GITHUB_TOKEN GitHub personal access token
--fix-title false Fix PR title to match first commit subject
--fix-body false Fix PR body to match commit message body
--fix-files false Fix files in PR using regex search/replace
--file-pattern Regex to match file paths (required w/ --fix-files)
--search-pattern Regex to search in files (required w/ --fix-files)
--replacement Replacement string for matched patterns
--remove-lines false Remove matching lines instead of replacing
--context-start Regex pattern for context start (for line removal)
--context-end Regex pattern for context end (for line removal)
--show-diff false Show unified diff output for file changes
--include-drafts false Include draft PRs in scan
--blocked-only false Process PRs that cannot merge
--dry-run false Preview changes without applying them
--workers -j 4 Number of parallel workers (1-32)
--verbose -v false Enable verbose output (DEBUG logs)
--quiet -q false Suppress output except errors
--log-level INFO Set logging level
--version Show version and exit

How It Works

  1. Scan Organization: Uses GitHub's GraphQL API to efficiently find blocked pull requests
  2. Fetch Commits: Retrieves the first commit from each PR using the REST API
  3. Parse Messages: Extracts commit subject and body, removing trailers
  4. Apply Changes: Updates PR titles and/or bodies in parallel
  5. Report Results: Shows summary of changes made

Trailers Removed

The following Git trailer patterns are automatically removed from PR bodies:

  • Signed-off-by:
  • Co-authored-by:
  • Reviewed-by:
  • Tested-by:
  • Acked-by:
  • Cc:
  • Reported-by:
  • Suggested-by:
  • Fixes:
  • See-also:
  • Link:
  • Bug:
  • Change-Id:

Authentication

You need a GitHub personal access token with appropriate permissions:

  1. Go to GitHub Settings โ†’ Developer settings โ†’ Personal access tokens
  2. Generate a new token with repo scope (or public_repo for public repos)
  3. Set the token as an environment variable:
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxx

Or pass it via the --token flag:

pull-request-fixer myorg --fix-title --token ghp_xxxxxxxxxxxxx

Examples

Example 1: Fix Titles in Organization

pull-request-fixer lfreleng-actions --fix-title

Output:

๐Ÿ” Scanning organization: lfreleng-actions
๐Ÿ”ง Will fix: titles

๐Ÿ“Š Found 15 blocked PRs to process

๐Ÿ” Blocked PRs:
   โ€ข lfreleng-actions/repo1#123: Update docs
   โ€ข lfreleng-actions/repo2#456: Fix bug
   ...

๐Ÿ”„ Processing: lfreleng-actions/repo1#123
   โœ… Updated title: docs: Add usage examples for CLI

๐Ÿ”„ Processing: lfreleng-actions/repo2#456
   โœ… Updated title: fix: Resolve authentication timeout issue

โœ… Fixed 15 PR(s)

Example 2: Dry Run with Both Fixes

pull-request-fixer myorg --fix-title --fix-body --dry-run

Output:

๐Ÿ” Scanning organization: myorg
๐Ÿ”ง Will fix: titles, bodies
๐Ÿƒ Dry run mode: no changes made

๐Ÿ“Š Found 5 blocked PRs to process

๐Ÿ”„ Processing: myorg/repo#123
   Would update title:
     From: Update documentation
     To:   docs: Add usage examples for CLI
   Would update body
     Length: 245 chars

โœ… [DRY RUN] Would fix 5 PR(s)

Example 3: High Performance Mode

For large organizations, use more workers:

pull-request-fixer bigorg --fix-title --fix-body --workers 16 --verbose

Performance

  • Parallel Processing: PRs processed concurrently for speed
  • Efficient Queries: GraphQL for scanning, REST for updates
  • Memory Efficient: Streaming results, no need to load all PRs
  • Typical Speed: 2-5 seconds per repository

Example timing for 100 repositories with 50 blocked PRs using 8 workers:

  • Organization scan: ~30-60 seconds
  • PR processing: ~20-30 seconds
  • Total: ~50-90 seconds

Example 4: Fix Files in a Blocked PR

This example removes invalid type: definitions from a GitHub composite action:

pull-request-fixer https://github.com/lfreleng-actions/make-action/pull/40 \
  --fix-files \
  --file-pattern './action.yaml' \
  --search-pattern '^\s+type:\s+\S' \
  --remove-lines \
  --context-start 'inputs:' \
  --context-end 'runs:' \
  --dry-run \
  --show-diff

Output:

๐Ÿ” Processing PR: https://github.com/lfreleng-actions/make-action/pull/40
๐Ÿ”ง Will fix: files
๐Ÿƒ Dry run mode: the tool will not apply changes

๐Ÿ“ Fixing files in PR...

โœ… Would fix 1 file ๐Ÿ“‚ action.yaml --- action.yaml +++ action.yaml @@ -10,7 +10,6 @@ repository: description: 'Remote Git repository URL' required: false

  • type: 'string' debug: description: 'Enable debug mode' required: false
  • type: 'boolean'

Dry-run completed!

Without `--dry-run`, the output would show:

```text
โœ… Updated 1 file
๐Ÿ”€ action.yaml
--- action.yaml
+++ action.yaml
@@ -10,7 +10,6 @@
   repository:
     description: 'Remote Git repository URL'
     required: false
-    type: 'string'
   debug:
     description: 'Enable debug mode'
     required: false
-    type: 'boolean'

This would:

  1. Clone the PR branch
  2. Remove all lines containing type: from the inputs: section of action.yaml
  3. Amend the last commit with the changes
  4. Force-push the updated commit
  5. Add a comment to the PR explaining the fix

Troubleshooting

No PRs Found

If the tool reports "No blocked PRs found", this could mean:

  • The organization truly has no blocked PRs
  • You may need to adjust the scanner's definition of "blocked"

Authentication Errors

If you see authentication errors:

Make sure your GITHUB_TOKEN environment variable contains a valid token

  • Verify the token has repo or public_repo scope
  • Check that the token hasn't expired

Rate Limiting

If you hit rate limits:

  • Reduce the number of workers: --workers 2
  • Wait for the rate limit to reset (shown in error message)
  • Use a token with higher rate limits

Permission Errors

If updates fail:

  • Ensure your token has write access to the repositories
  • Check that you're not trying to update PRs in archived repos
  • Verify the PRs are not locked

Development

Setup

git clone https://github.com/lfit/pull-request-fixer.git
cd pull-request-fixer
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"

Running Tests

pytest

Running Pre-commit Hooks

pre-commit install
pre-commit run --all-files

Code Style

The project uses:

  • ruff for linting and formatting
  • mypy for type checking
  • pytest for testing

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Ensure all tests pass
  6. Submit a pull request

License

Apache-2.0

Support

Related Projects

Acknowledgments

This project uses patterns from:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pull_request_fixer-0.1.3.tar.gz (52.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pull_request_fixer-0.1.3-py3-none-any.whl (56.5 kB view details)

Uploaded Python 3

File details

Details for the file pull_request_fixer-0.1.3.tar.gz.

File metadata

  • Download URL: pull_request_fixer-0.1.3.tar.gz
  • Upload date:
  • Size: 52.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for pull_request_fixer-0.1.3.tar.gz
Algorithm Hash digest
SHA256 dc92805235672af17ad6b6757155f66a045785fc2ec2e2ac7cf2f8256eb17a12
MD5 40c44fe92a02f2f13b732d03a722871e
BLAKE2b-256 23f1418a58b53b3a8f80fe1d36efdf21dbad95fa57a81df139a9c96379e45d87

See more details on using hashes here.

Provenance

The following attestation bundles were made for pull_request_fixer-0.1.3.tar.gz:

Publisher: build-test-release.yaml on lfreleng-actions/pull-request-fixer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pull_request_fixer-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for pull_request_fixer-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c068dd2880bc8aea1b6b94b04549532182f381ec55166d926aae1127892cc8f7
MD5 e77296cf5fa67ae1ff5974b3324ccf6c
BLAKE2b-256 4f4f4a90a7c3e793919a7485034cab118f52c81f58b723107e66b540b21f73fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for pull_request_fixer-0.1.3-py3-none-any.whl:

Publisher: build-test-release.yaml on lfreleng-actions/pull-request-fixer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page