Comprehensive duplicate PR prevention system: 1. Pre-flight check scripts: - scripts/check-existing-prs.sh (Bash) - scripts/check_existing_prs.py (Python) - scripts/pr-safe.sh (user-friendly wrapper) 2. Documentation: - docs/duplicate-pr-prevention.md Prevents duplicate PRs by checking for existing PRs before creating new ones. Exit codes: - 0: Safe to create PR - 1: Existing PRs found (stop) - 2: Error Closes #1524
112 lines
3.4 KiB
Python
112 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
check_existing_prs.py — Pre-flight check for duplicate PRs (Python version)
|
|
|
|
Usage:
|
|
python scripts/check_existing_prs.py <issue_number>
|
|
python scripts/check_existing_prs.py --issue 1524
|
|
|
|
Exit codes:
|
|
0: No existing PRs found (safe to create new PR)
|
|
1: Existing PRs found (do not create new PR)
|
|
2: Error (API failure, missing parameters, etc.)
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
import urllib.error
|
|
from pathlib import Path
|
|
|
|
# Configuration
|
|
GITEA_URL = os.environ.get("GITEA_URL", "https://forge.alexanderwhitestone.com")
|
|
REPO = os.environ.get("GITEA_REPO", "Timmy_Foundation/the-nexus")
|
|
TOKEN_PATH = Path.home() / ".config" / "gitea" / "token"
|
|
|
|
# ANSI colors
|
|
RED = "\033[0;31m"
|
|
GREEN = "\033[0;32m"
|
|
YELLOW = "\033[1;33m"
|
|
NC = "\033[0m"
|
|
|
|
|
|
def load_token() -> str:
|
|
"""Load Gitea API token."""
|
|
if TOKEN_PATH.exists():
|
|
return TOKEN_PATH.read_text().strip()
|
|
return os.environ.get("GITEA_TOKEN", "")
|
|
|
|
|
|
def check_existing_prs(issue_number: int, token: str) -> list:
|
|
"""Check if issue already has open PRs.
|
|
|
|
Returns list of existing PR dicts, or empty list if none found.
|
|
"""
|
|
url = f"{GITEA_URL}/api/v1/repos/{REPO}/pulls?state=open"
|
|
headers = {
|
|
"Authorization": f"token {token}",
|
|
"Accept": "application/json",
|
|
}
|
|
|
|
try:
|
|
req = urllib.request.Request(url, headers=headers)
|
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
prs = json.loads(resp.read())
|
|
except urllib.error.HTTPError as e:
|
|
print(f"{RED}Error: API request failed with HTTP {e.code}{NC}", file=sys.stderr)
|
|
return []
|
|
except Exception as e:
|
|
print(f"{RED}Error: {e}{NC}", file=sys.stderr)
|
|
return []
|
|
|
|
# Find PRs referencing this issue
|
|
issue_ref = f"#{issue_number}"
|
|
existing = []
|
|
for pr in prs:
|
|
body = (pr.get("body") or "") + " " + (pr.get("title") or "")
|
|
if issue_ref in body:
|
|
existing.append(pr)
|
|
|
|
return existing
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Check for existing PRs before creating new one")
|
|
parser.add_argument("issue_number", nargs="?", type=int, help="Issue number to check")
|
|
parser.add_argument("--issue", "-i", type=int, help="Issue number (alternative syntax)")
|
|
args = parser.parse_args()
|
|
|
|
issue_number = args.issue_number or args.issue
|
|
if not issue_number:
|
|
print(f"{RED}Error: Issue number required{NC}", file=sys.stderr)
|
|
print("Usage: python check_existing_prs.py <issue_number>", file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
# Load token
|
|
token = load_token()
|
|
if not token:
|
|
print(f"{RED}Error: Gitea token not found{NC}", file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
# Check for existing PRs
|
|
print(f"{YELLOW}Checking for existing PRs referencing issue #{issue_number}...{NC}")
|
|
existing = check_existing_prs(issue_number, token)
|
|
|
|
if existing:
|
|
print(f"{RED}✗ Found existing PRs for issue #{issue_number}:{NC}")
|
|
for pr in existing:
|
|
print(f" #{pr['number']}: {pr['title']} ({pr['head']['ref']})")
|
|
print()
|
|
print(f"{YELLOW}Do not create another PR. Review existing PRs instead.{NC}")
|
|
sys.exit(1)
|
|
else:
|
|
print(f"{GREEN}✓ No existing PRs found for issue #{issue_number}{NC}")
|
|
print(f"{GREEN}Safe to create new PR.{NC}")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|