Files
gh-atournayre-claude-market…/skills/git-pr/scripts/assign_milestone.py
2025-11-29 17:58:54 +08:00

147 lines
4.3 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Lister milestones et gérer assignation
Usage: assign_milestone.py <pr_number> [--milestone <name>]
Output: Milestone assigné ou "ignored" (stdout)
"""
import argparse
import json
import subprocess
import sys
import re
from milestone_cache import MilestoneCache
def get_repo_info():
"""Récupère owner/repo depuis git remote"""
try:
result = subprocess.run(
["git", "remote", "get-url", "origin"],
capture_output=True,
text=True,
check=True
)
url = result.stdout.strip()
# Extraire owner/repo depuis URL GitHub
match = re.search(r'github\.com[:/](.+/.+?)(?:\.git)?$', url)
if match:
return match.group(1)
raise ValueError(f"Format URL invalide: {url}")
except Exception as e:
print(f"❌ Erreur récupération repo: {e}", file=sys.stderr)
sys.exit(1)
def get_open_milestones(repo):
"""Récupère les milestones ouverts via gh API"""
try:
result = subprocess.run(
["gh", "api", f"repos/{repo}/milestones", "--jq",
"[.[] | select(.state == \"open\") | {number, title}]"],
capture_output=True,
text=True,
check=True
)
return json.loads(result.stdout)
except subprocess.CalledProcessError as e:
print(f"❌ Erreur récupération milestones: {e.stderr}", file=sys.stderr)
return []
except json.JSONDecodeError as e:
print(f"❌ Erreur parsing JSON milestones: {e}", file=sys.stderr)
return []
def get_open_milestones_cached(repo):
"""Récupère milestones avec cache"""
cache = MilestoneCache()
if not cache.cache.get("milestones"):
milestones = get_open_milestones(repo)
cache.refresh_from_api(milestones)
return milestones
return cache.cache["milestones"]
def find_milestone(repo, query):
"""Cherche milestone par query (exact ou partiel)"""
cache = MilestoneCache()
result = cache.find(query)
if result:
return result
normalized = cache.normalize_semver(query)
result = cache.find(normalized)
if result:
return result
milestones = get_open_milestones(repo)
cache.refresh_from_api(milestones)
result = cache.find(normalized)
if result:
return result
return cache.create(normalized)
def assign_milestone(pr_number, milestone_title):
"""Assigne un milestone à la PR"""
try:
subprocess.run(
["gh", "pr", "edit", str(pr_number), "--milestone", milestone_title],
capture_output=True,
text=True,
check=True
)
return True
except subprocess.CalledProcessError as e:
print(f"❌ Erreur assignation milestone: {e.stderr}", file=sys.stderr)
return False
def main():
parser = argparse.ArgumentParser(description="Assigne un milestone à une PR")
parser.add_argument("pr_number", type=int, help="Numéro de la PR")
parser.add_argument("--milestone", help="Nom du milestone à assigner")
args = parser.parse_args()
repo = get_repo_info()
milestones = get_open_milestones_cached(repo)
if not milestones:
print(" Aucun milestone ouvert - ignoré")
print("ignored")
sys.exit(0)
# Si --milestone fourni, utiliser find_milestone pour recherche intelligente
if args.milestone:
try:
milestone = find_milestone(repo, args.milestone)
if assign_milestone(args.pr_number, milestone['title']):
print(f"✅ Milestone '{milestone['title']}' assigné")
print(milestone['title'])
sys.exit(0)
else:
sys.exit(1)
except Exception as e:
print(f"❌ Erreur: {e}", file=sys.stderr)
sys.exit(1)
# Sinon, retourner JSON pour AskUserQuestion
# Suggérer le premier milestone (généralement le plus récent)
milestones_with_suggestion = [
{
"number": m["number"],
"title": m["title"],
"is_suggested": i == 0
}
for i, m in enumerate(milestones)
]
output = {
"milestones": milestones_with_suggestion,
"needs_user_input": True
}
print(json.dumps(output, indent=2))
if __name__ == "__main__":
main()