Skip to content

Commit bfceecb

Browse files
committed
Update maintenance workflow to update master issue periodically
1 parent 8e6ad08 commit bfceecb

2 files changed

Lines changed: 124 additions & 0 deletions

File tree

‎.github/workflows/maintenance.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ on:
99

1010
permissions:
1111
contents: write
12+
issues: write
13+
pull-requests: read
1214

1315
# Shared with sync-cpython.yml so the two never push at the same time.
1416
concurrency:
@@ -44,6 +46,12 @@ jobs:
4446
- name: Update contributor chart and README stats block
4547
run: python3 scripts/contributor_chart.py
4648

49+
- name: Update master issue with pending review items
50+
env:
51+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52+
MASTER_ISSUE: '83'
53+
run: python3 scripts/update_master_issue.py
54+
4755
- name: Stage changes
4856
run: git add --all
4957

‎scripts/update_master_issue.py‎

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python3
2+
"""Keep the "pending review" section of the master issue in sync.
3+
4+
Lists every open issue carrying the sync label (created by the
5+
"Sync with CPython" workflow) and rewrites that section of the master issue.
6+
If an issue has an open linked PR, "درخواست ادغام: #<PR>" is added next to it.
7+
8+
Only the text between the two HTML-comment markers is ever touched. On the
9+
first run the markers are inserted right below the section heading.
10+
11+
Env: GH_TOKEN, GITHUB_REPOSITORY (set by Actions); optional MASTER_ISSUE,
12+
REVIEW_LABEL.
13+
"""
14+
import json
15+
import os
16+
import re
17+
import subprocess
18+
import sys
19+
import tempfile
20+
21+
REPO = os.environ["GITHUB_REPOSITORY"]
22+
MASTER = os.environ.get("MASTER_ISSUE", "83")
23+
LABEL = os.environ.get("REVIEW_LABEL", "تغییرات خودکار در ترجمه‌ها")
24+
HEADING = "### تغییرات خودکارِ در انتظار بررسی"
25+
START = "<!-- auto-review:start -->"
26+
END = "<!-- auto-review:end -->"
27+
28+
QUERY = """
29+
query($owner: String!, $name: String!, $label: String!) {
30+
repository(owner: $owner, name: $name) {
31+
issues(first: 100, states: OPEN, labels: [$label],
32+
orderBy: {field: CREATED_AT, direction: ASC}) {
33+
nodes {
34+
number
35+
closedByPullRequestsReferences(first: 5, includeClosedPrs: false) {
36+
nodes { number state }
37+
}
38+
}
39+
}
40+
}
41+
}
42+
"""
43+
44+
45+
def gh(*args):
46+
return subprocess.run(
47+
["gh", *args], check=True, capture_output=True, text=True
48+
).stdout
49+
50+
51+
def fetch_items():
52+
owner, name = REPO.split("/", 1)
53+
out = gh(
54+
"api", "graphql",
55+
"-f", f"query={QUERY}",
56+
"-F", f"owner={owner}", "-F", f"name={name}", "-f", f"label={LABEL}",
57+
)
58+
nodes = json.loads(out)["data"]["repository"]["issues"]["nodes"]
59+
items = []
60+
for n in nodes:
61+
if str(n["number"]) == str(MASTER):
62+
continue
63+
prs = [
64+
p["number"]
65+
for p in n["closedByPullRequestsReferences"]["nodes"]
66+
if p["state"] == "OPEN"
67+
]
68+
items.append((n["number"], prs))
69+
return items
70+
71+
72+
def render(items):
73+
if not items:
74+
return "_موردی در انتظار بررسی نیست._"
75+
lines = []
76+
for number, prs in items:
77+
line = f"- #{number}"
78+
if prs:
79+
line += " — درخواست ادغام: " + "، ".join(f"#{p}" for p in prs)
80+
lines.append(line)
81+
return "\n".join(lines)
82+
83+
84+
def splice(body, block):
85+
body = body.replace("\r\n", "\n")
86+
section = f"{START}\n{block}\n{END}"
87+
if START in body and END in body:
88+
pattern = re.compile(re.escape(START) + r".*?" + re.escape(END), re.S)
89+
return pattern.sub(lambda _: section, body, count=1)
90+
91+
lines = body.split("\n")
92+
for i, line in enumerate(lines):
93+
if line.strip() == HEADING:
94+
lines[i + 1:i + 1] = ["", section]
95+
return "\n".join(lines)
96+
raise SystemExit(f"Heading not found in issue #{MASTER}: {HEADING}")
97+
98+
99+
def main():
100+
body = gh("issue", "view", str(MASTER), "--repo", REPO,
101+
"--json", "body", "-q", ".body")
102+
old = body.replace("\r\n", "\n").rstrip("\n")
103+
new = splice(body, render(fetch_items())).rstrip("\n")
104+
105+
if new == old:
106+
print("Master issue already up to date.")
107+
return
108+
with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False,
109+
encoding="utf-8") as f:
110+
f.write(new + "\n")
111+
gh("issue", "edit", str(MASTER), "--repo", REPO, "--body-file", f.name)
112+
print(f"Updated issue #{MASTER}.")
113+
114+
115+
if __name__ == "__main__":
116+
sys.exit(main())

0 commit comments

Comments
 (0)