mirror of
https://github.com/PX4/PX4-Autopilot.git
synced 2026-03-24 10:34:00 +08:00
Add CI enforcement of conventional commit format for PR titles and commit messages. Includes three Python scripts under Tools/ci/: - conventional_commits.py: shared parsing/validation library - check_pr_title.py: validates PR title format, suggests fixes - check_commit_messages.py: checks commits for blocking errors (fixup/squash/WIP leftovers) and advisory warnings (review-response, formatter-only commits) The workflow (.github/workflows/commit_checks.yml) posts concise GitHub PR comments with actionable suggestions and auto-removes them once issues are resolved. Also updates CONTRIBUTING.md and docs with the conventional commits convention. Signed-off-by: Ramon Roche <mrpollo@gmail.com>
96 lines
3.1 KiB
Python
Executable File
96 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Post, update, or delete a PR comment with deduplication.
|
|
|
|
Uses hidden HTML markers to find existing comments and avoid duplicates.
|
|
Reads comment body from stdin when posting or updating.
|
|
|
|
Usage:
|
|
echo "comment body" | python3 pr_comment.py --marker pr-title --pr 123 --result fail
|
|
python3 pr_comment.py --marker pr-title --pr 123 --result pass
|
|
|
|
Results:
|
|
fail - post/update comment with body from stdin
|
|
warn - post/update comment with body from stdin
|
|
pass - delete existing comment if any
|
|
|
|
Requires GH_TOKEN and GITHUB_REPOSITORY environment variables.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
def gh_api(endpoint: str, method: str = 'GET', body: dict | None = None) -> str:
|
|
"""Call the GitHub API via gh cli."""
|
|
cmd = ['gh', 'api', endpoint, '-X', method]
|
|
if body:
|
|
for key, value in body.items():
|
|
cmd.extend(['-f', f'{key}={value}'])
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
if result.returncode != 0 and method != 'DELETE':
|
|
print(f"gh api error: {result.stderr}", file=sys.stderr)
|
|
return result.stdout
|
|
|
|
|
|
def find_comment(repo: str, pr: int, marker: str) -> str | None:
|
|
"""Find an existing comment by its hidden marker. Returns comment ID or None."""
|
|
response = gh_api(f"repos/{repo}/issues/{pr}/comments?per_page=100")
|
|
try:
|
|
comments = json.loads(response)
|
|
except json.JSONDecodeError:
|
|
return None
|
|
|
|
if not isinstance(comments, list):
|
|
return None
|
|
|
|
for comment in comments:
|
|
if isinstance(comment, dict) and comment.get('body', '').startswith(marker):
|
|
return str(comment['id'])
|
|
return None
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description='Manage PR quality comments')
|
|
parser.add_argument('--marker', required=True,
|
|
help='Marker name (e.g. pr-title, commit-msgs, pr-body)')
|
|
parser.add_argument('--pr', required=True, type=int,
|
|
help='Pull request number')
|
|
parser.add_argument('--result', required=True, choices=['pass', 'fail', 'warn'],
|
|
help='Check result: pass deletes comment, fail/warn posts it')
|
|
args = parser.parse_args()
|
|
|
|
repo = os.environ.get('GITHUB_REPOSITORY', '')
|
|
if not repo:
|
|
print("GITHUB_REPOSITORY not set", file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
marker = f"<!-- commit-quality-{args.marker} -->"
|
|
existing_id = find_comment(repo, args.pr, marker)
|
|
|
|
if args.result == 'pass':
|
|
if existing_id:
|
|
gh_api(f"repos/{repo}/issues/comments/{existing_id}", method='DELETE')
|
|
return
|
|
|
|
# Read comment body from stdin
|
|
body_content = sys.stdin.read().strip()
|
|
if not body_content:
|
|
print("No comment body provided on stdin", file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
full_body = f"{marker}\n{body_content}"
|
|
|
|
if existing_id:
|
|
gh_api(f"repos/{repo}/issues/comments/{existing_id}", method='PATCH',
|
|
body={'body': full_body})
|
|
else:
|
|
gh_api(f"repos/{repo}/issues/{args.pr}/comments", method='POST',
|
|
body={'body': full_body})
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|