110 lines
3.5 KiB
YAML
110 lines
3.5 KiB
YAML
name: Sync Labels
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
push:
|
|
paths:
|
|
- ".github/labels.yml"
|
|
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
|
|
jobs:
|
|
sync:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.11"
|
|
|
|
- name: Install dependencies
|
|
run: python -m pip install --disable-pip-version-check pyyaml
|
|
|
|
- name: Sync labels
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
run: |
|
|
python - <<'PY'
|
|
import json
|
|
import os
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
import yaml
|
|
|
|
token = os.environ["GITHUB_TOKEN"]
|
|
owner_repo = os.environ["GITHUB_REPOSITORY"]
|
|
base_url = f"https://api.github.com/repos/{owner_repo}"
|
|
|
|
with open(".github/labels.yml", "r", encoding="utf-8") as fh:
|
|
config = yaml.safe_load(fh) or {}
|
|
|
|
desired_labels = config.get("labels", [])
|
|
desired_by_name = {label["name"]: label for label in desired_labels}
|
|
|
|
headers = {
|
|
"Accept": "application/vnd.github+json",
|
|
"Authorization": f"Bearer {token}",
|
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
"User-Agent": "mirofish-label-sync",
|
|
}
|
|
|
|
def request(method, url, payload=None):
|
|
data = None
|
|
if payload is not None:
|
|
data = json.dumps(payload).encode("utf-8")
|
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
with urllib.request.urlopen(req) as response:
|
|
raw = response.read()
|
|
return json.loads(raw.decode("utf-8")) if raw else None
|
|
|
|
existing = []
|
|
page = 1
|
|
while True:
|
|
url = f"{base_url}/labels?per_page=100&page={page}"
|
|
batch = request("GET", url)
|
|
if not batch:
|
|
break
|
|
existing.extend(batch)
|
|
if len(batch) < 100:
|
|
break
|
|
page += 1
|
|
|
|
existing_by_name = {label["name"]: label for label in existing}
|
|
|
|
for name, desired in desired_by_name.items():
|
|
payload = {
|
|
"new_name": desired["name"],
|
|
"color": desired["color"],
|
|
"description": desired["description"],
|
|
}
|
|
if name in existing_by_name:
|
|
url = f"{base_url}/labels/{urllib.parse.quote(name, safe='')}"
|
|
request("PATCH", url, payload)
|
|
print(f"updated {name}")
|
|
else:
|
|
url = f"{base_url}/labels"
|
|
request("POST", url, {
|
|
"name": desired["name"],
|
|
"color": desired["color"],
|
|
"description": desired["description"],
|
|
})
|
|
print(f"created {name}")
|
|
|
|
for name in sorted(existing_by_name):
|
|
if name not in desired_by_name:
|
|
url = f"{base_url}/labels/{urllib.parse.quote(name, safe='')}"
|
|
try:
|
|
request("DELETE", url)
|
|
print(f"deleted {name}")
|
|
except urllib.error.HTTPError as exc:
|
|
raise RuntimeError(f"Failed to delete label '{name}': {exc.read().decode('utf-8')}") from exc
|
|
PY
|