112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Update Vikunja task after a merge to master.
|
|
|
|
Usage:
|
|
VIKUNJA_TOKEN=<token> python3 scripts/post_merge.py <commit_msg>
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import requests
|
|
|
|
VIKUNJA_API = "https://work.oblachno.oblachno.fyi/api/v1"
|
|
TASK_ID_RE = re.compile(r"GRM-\d+")
|
|
PROJECT_ID = 6
|
|
|
|
|
|
def extract_task_id(commit_msg: str) -> str:
|
|
"""Extract GRM-N task identifier from the first line of commit message."""
|
|
first_line = commit_msg.split("\n")[0]
|
|
match = TASK_ID_RE.search(first_line)
|
|
return match.group(0) if match else ""
|
|
|
|
|
|
def extract_conventional_msg(commit_msg: str) -> str:
|
|
"""Strip the GRM-N prefix from the commit subject."""
|
|
first_line = commit_msg.split("\n")[0]
|
|
return re.sub(r"^GRM-\d+:\s*", "", first_line)
|
|
|
|
|
|
def resolve_task_id(token: str, task_id: str) -> int:
|
|
"""Resolve GRM-N identifier to Vikunja numeric task ID."""
|
|
url = f"{VIKUNJA_API}/tasks/all"
|
|
headers = {"Authorization": f"Bearer {token}"}
|
|
params = {"per_page": 50}
|
|
response = requests.get(url, headers=headers, params=params, timeout=30)
|
|
response.raise_for_status()
|
|
tasks = response.json()
|
|
matches = [
|
|
t for t in tasks
|
|
if t.get("project_id") == PROJECT_ID and t.get("identifier") == task_id
|
|
]
|
|
if not matches:
|
|
print(f"ERROR: Could not find Vikunja task for {task_id} in project {PROJECT_ID}")
|
|
sys.exit(1)
|
|
return int(matches[0]["id"])
|
|
|
|
|
|
def post_comment(token: str, task_id: int, html: str) -> None:
|
|
"""Post an HTML comment to a Vikunja task."""
|
|
url = f"{VIKUNJA_API}/tasks/{task_id}/comments"
|
|
headers = {
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
payload = {"comment": html}
|
|
response = requests.post(url, headers=headers, json=payload, timeout=30)
|
|
response.raise_for_status()
|
|
|
|
|
|
def mark_task_done(token: str, task_id: int) -> None:
|
|
"""Mark a Vikunja task as done."""
|
|
url = f"{VIKUNJA_API}/tasks/{task_id}"
|
|
headers = {
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
payload = {"done": True}
|
|
response = requests.put(url, headers=headers, json=payload, timeout=30)
|
|
response.raise_for_status()
|
|
|
|
|
|
def build_comment(task_id: str, conv_msg: str, commit_sha: str) -> str:
|
|
"""Build HTML comment body for Vikunja."""
|
|
return (
|
|
f"<p><strong>{task_id}</strong>: {conv_msg}</p>"
|
|
f"<p>Commit: <code>{commit_sha}</code></p>"
|
|
)
|
|
|
|
|
|
def main(args: list[str] | None = None) -> None: # pragma: no cover
|
|
argv = args if args is not None else sys.argv
|
|
parser = argparse.ArgumentParser(description="Update Vikunja task after merge")
|
|
parser.add_argument("commit_msg", help="Full merge commit message")
|
|
parser.add_argument("--commit-sha", default="", help="Commit SHA")
|
|
parsed = parser.parse_args(argv[1:])
|
|
|
|
token = os.environ.get("VIKUNJA_TOKEN", "")
|
|
if not token:
|
|
print("ERROR: VIKUNJA_TOKEN is not set.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
task_id = extract_task_id(parsed.commit_msg)
|
|
if not task_id:
|
|
print("No task ID in commit message, skipping Vikunja update.")
|
|
return
|
|
|
|
vikunja_task_id = resolve_task_id(token, task_id)
|
|
conv_msg = extract_conventional_msg(parsed.commit_msg)
|
|
commit_sha = parsed.commit_sha or "unknown"
|
|
html = build_comment(task_id, conv_msg, commit_sha)
|
|
|
|
post_comment(token, vikunja_task_id, html)
|
|
mark_task_done(token, vikunja_task_id)
|
|
print(f"Vikunja task {task_id} (ID {vikunja_task_id}) updated and marked done.")
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
main()
|