Skip to content

lfreleng-actions/pull-request-fixer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

131 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ› οΈ 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/lfreleng-actions/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/lfreleng-actions/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:

About

Scans a GitHub organisation and performs basic fixes across all open pull requests

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors