| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,8 @@ on: | |||
| 9 | 9 | ||
| 10 | 10 | permissions: | |
| 11 | 11 | contents: write | |
| 12 | + issues: write | ||
| 13 | + pull-requests: read | ||
| 12 | 14 | ||
| 13 | 15 | # Shared with sync-cpython.yml so the two never push at the same time. | |
| 14 | 16 | concurrency: | |
@@ -44,6 +46,12 @@ jobs: | |||
| 44 | 46 | - name: Update contributor chart and README stats block | |
| 45 | 47 | run: python3 scripts/contributor_chart.py | |
| 46 | 48 | ||
| 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 | + | ||
| 47 | 55 | - name: Stage changes | |
| 48 | 56 | run: git add --all | |
| 49 | 57 | ||
| Original file line number | Diff line number | Diff 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()) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments