CI: open an issue when upstream publishes a release not yet imported
ci / build (pull_request) Successful in 7m22s
ci / build (pull_request) Successful in 7m22s
Reads metadata only: upstream's releases list from GitHub's API and the head of the upstream branch from Gitea's. Nothing of upstream's is fetched, so its history can't land here. Daily at 06:17 UTC.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# Watch upstream for releases the fork hasn't imported yet, and open an issue
|
||||
# for each one so it waits in the tracker until someone strips it in.
|
||||
#
|
||||
# Reads metadata only -- the releases list from GitHub's API and the head of
|
||||
# this repo's `upstream` branch from Gitea's. Nothing of upstream's is fetched,
|
||||
# so none of its history (which carries the Enterprise code) can land here.
|
||||
# Importing is still by hand: tools/fork/strip.py onto `upstream`, then merge,
|
||||
# as docs/spec/SPEC.md §2.2 and §2.2a describe.
|
||||
#
|
||||
# The imported base is the tag in the `upstream` branch's head commit subject
|
||||
# ("Import upstream v0.16.22, stripped"). Drafts and pre-releases are ignored.
|
||||
# An issue is opened once per release: an existing one with the same title,
|
||||
# open or closed, stops a second.
|
||||
#
|
||||
# Daily 06:17 UTC; run it by hand with workflow_dispatch.
|
||||
name: upstream-watch
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: upstream-watch
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
upstream-watch:
|
||||
runs-on: light
|
||||
container:
|
||||
image: python:3.13-slim@sha256:8d9d0b8bcf6506481eae4907c18f5e3e7902e629f5f6d684f9e7c32e85e3ddf0 # 3.13-slim
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json, os, re, sys, urllib.request
|
||||
|
||||
api = f"{os.environ['CI_SERVER_INTERNAL']}/api/v1/repos/{os.environ['REPO']}"
|
||||
def call(method, url, body=None, token=os.environ["TOKEN"]):
|
||||
headers = {"Content-Type": "application/json", "User-Agent": "inbuxa-upstream-watch"}
|
||||
if token:
|
||||
headers["Authorization"] = f"token {token}"
|
||||
req = urllib.request.Request(url, method=method, headers=headers,
|
||||
data=json.dumps(body).encode() if body is not None else None)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return json.load(r)
|
||||
SEMVER = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$")
|
||||
def key(tag):
|
||||
return tuple(int(x) for x in SEMVER.match(tag).groups())
|
||||
|
||||
subject = call("GET", f"{api}/branches/upstream")["commit"]["message"].splitlines()[0]
|
||||
m = re.search(r"\bupstream (v\d+\.\d+\.\d+)\b", subject)
|
||||
if not m:
|
||||
print(f"Can't read the imported base from the upstream branch: {subject!r}", file=sys.stderr); sys.exit(1)
|
||||
base = m.group(1)
|
||||
|
||||
# Unauthenticated: a public repo, once a day, well inside the limit.
|
||||
rels = call("GET", "https://api.github.com/repos/stalwartlabs/stalwart/releases?per_page=30", token=None)
|
||||
newer = sorted((r for r in rels
|
||||
if not r["draft"] and not r["prerelease"] and SEMVER.match(r["tag_name"])
|
||||
and key(r["tag_name"]) > key(base)),
|
||||
key=lambda r: key(r["tag_name"]))
|
||||
if not newer:
|
||||
print(f"Up to date: {base} is the newest upstream release."); sys.exit(0)
|
||||
|
||||
# Titles and bodies stay free of the upstream project's name, as the
|
||||
# rest of the fork's user-visible text does.
|
||||
existing = {i["title"] for i in call("GET", f"{api}/issues?state=all&type=issues&q=Import+upstream&limit=50")}
|
||||
for r in newer:
|
||||
tag = r["tag_name"]
|
||||
title = f"Import upstream {tag}"
|
||||
if title in existing:
|
||||
print(f"{tag}: issue already exists."); continue
|
||||
body = (f"Upstream published {tag} on {r['published_at'][:10]}. "
|
||||
f"The fork's imported base is {base}.\n\n"
|
||||
"Import it as tools/fork/README.md describes:\n\n"
|
||||
"```bash\n"
|
||||
"git -C \"$UPSTREAM_CLONE\" fetch --tags\n"
|
||||
f"tools/fork/strip.py --upstream \"$UPSTREAM_CLONE\" --ref {tag} --out /tmp/strip-{tag}\n"
|
||||
"```\n\n"
|
||||
"Commit the stripped tree to `upstream` with the strip report in the message, "
|
||||
"add any new third-party notices to `THIRD-PARTY.md`, then merge `upstream` into `main`.")
|
||||
issue = call("POST", f"{api}/issues", {"title": title, "body": body})
|
||||
print(f"{tag}: opened #{issue['number']}.")
|
||||
PY
|
||||
Reference in New Issue
Block a user