Skip to content

Commit aaa1a3e

Browse files
committed
first cut
1 parent 832b303 commit aaa1a3e

File tree

2 files changed

+192
-7
lines changed

2 files changed

+192
-7
lines changed

.github/scripts/assign_xls_number.py

Lines changed: 171 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import os
1111
import re
1212
import sys
13+
import json
1314
from pathlib import Path
15+
from urllib import error, parse, request
1416

1517

1618
def get_existing_xls_numbers(repo_root: Path) -> set[int]:
@@ -74,6 +76,138 @@ def find_draft_xls_files(changed_files: list[str]) -> list[str]:
7476
return list(set(drafts)) # Remove duplicates
7577

7678

79+
def github_api_request(path: str, token: str, params: dict | None = None):
80+
"""Small helper to call the GitHub REST API.
81+
82+
Returns parsed JSON or an empty list/dict on failure.
83+
"""
84+
85+
base_url = "https://api.github.com"
86+
url = f"{base_url}{path}"
87+
if params:
88+
query = parse.urlencode(params)
89+
url = f"{url}?{query}"
90+
91+
headers = {
92+
"Accept": "application/vnd.github+json",
93+
"User-Agent": "xrpl-standards-xls-number-bot",
94+
}
95+
if token:
96+
headers["Authorization"] = f"Bearer {token}"
97+
98+
req = request.Request(url, headers=headers)
99+
100+
try:
101+
with request.urlopen(req) as resp:
102+
data = resp.read()
103+
return json.loads(data.decode("utf-8"))
104+
except error.HTTPError as e:
105+
print(f"Warning: GitHub API HTTP error {e.code} for {url}: {e.reason}")
106+
except error.URLError as e:
107+
print(f"Warning: GitHub API URL error for {url}: {e.reason}")
108+
109+
# On any failure, return an empty list so callers can treat it as "no data".
110+
return []
111+
112+
113+
def extract_xls_number_from_comments(
114+
owner: str, repo: str, token: str, issue_number: int
115+
) -> int | None:
116+
"""Extract reserved XLS number for an issue/PR from bot comments.
117+
118+
Looks for a marker of the form: <!-- XLS_NUMBER:0123 --> in comments
119+
authored by github-actions[bot].
120+
"""
121+
122+
page = 1
123+
while True:
124+
comments = github_api_request(
125+
f"/repos/{owner}/{repo}/issues/{issue_number}/comments",
126+
token,
127+
{"per_page": 100, "page": page},
128+
)
129+
130+
if not comments:
131+
break
132+
133+
for comment in comments:
134+
if comment.get("user", {}).get("login") != "github-actions[bot]":
135+
continue
136+
body = comment.get("body") or ""
137+
match = re.search(r"<!--\s*XLS_NUMBER:(\\d+)\s*-->", body)
138+
if match:
139+
try:
140+
return int(match.group(1))
141+
except ValueError:
142+
continue
143+
144+
if len(comments) < 100:
145+
break
146+
page += 1
147+
148+
return None
149+
150+
151+
def get_reserved_xls_numbers_from_prs(
152+
token: str, repo: str, current_pr_number: int | None
153+
) -> tuple[set[int], int | None]:
154+
"""Find XLS numbers reserved by open PRs with the 'has-xls-number' label.
155+
156+
Returns a tuple of (set of reserved numbers, number assigned to the current PR
157+
if any).
158+
"""
159+
160+
if not token or not repo:
161+
# Without a token or repo, we cannot query the API; treat as no reservations.
162+
return set(), None
163+
164+
if "/" not in repo:
165+
return set(), None
166+
167+
owner, repo_name = repo.split("/", 1)
168+
169+
reserved_numbers: set[int] = set()
170+
current_pr_assigned: int | None = None
171+
172+
page = 1
173+
while True:
174+
issues = github_api_request(
175+
f"/repos/{owner}/{repo_name}/issues",
176+
token,
177+
{
178+
"state": "open",
179+
"labels": "has-xls-number",
180+
"per_page": 100,
181+
"page": page,
182+
},
183+
)
184+
185+
if not issues:
186+
break
187+
188+
for issue in issues:
189+
# Filter to PRs only
190+
if "pull_request" not in issue:
191+
continue
192+
193+
issue_number = issue.get("number")
194+
num = extract_xls_number_from_comments(owner, repo_name, token, issue_number)
195+
if num is None:
196+
continue
197+
198+
reserved_numbers.add(num)
199+
200+
if current_pr_number is not None and issue_number == current_pr_number:
201+
current_pr_assigned = num
202+
203+
if len(issues) < 100:
204+
break
205+
206+
page += 1
207+
208+
return reserved_numbers, current_pr_assigned
209+
210+
77211
def main():
78212
"""Main entry point for the script."""
79213
# Get repository root (parent of .github directory)
@@ -110,24 +244,56 @@ def set_github_output(name: str, value: str):
110244
set_github_output("has_drafts", "false")
111245
return
112246

113-
# Get existing XLS numbers
247+
# Discover reserved XLS numbers from other open PRs and from this PR (if any)
248+
github_token = os.environ.get("GITHUB_TOKEN", "")
249+
github_repo = os.environ.get("GITHUB_REPOSITORY", "")
250+
current_pr_number = None
251+
event_path = os.environ.get("GITHUB_EVENT_PATH")
252+
if event_path and os.path.isfile(event_path):
253+
try:
254+
with open(event_path, "r", encoding="utf-8") as f:
255+
event = json.load(f)
256+
current_pr_number = event.get("pull_request", {}).get("number")
257+
except Exception as exc: # pragma: no cover - defensive
258+
print(f"Warning: Failed to parse GITHUB_EVENT_PATH: {exc}")
259+
260+
reserved_numbers, current_pr_assigned = get_reserved_xls_numbers_from_prs(
261+
github_token,
262+
github_repo,
263+
current_pr_number,
264+
)
265+
266+
if reserved_numbers:
267+
print(f"Found {len(reserved_numbers)} XLS numbers reserved by open PRs.")
268+
269+
# Get existing XLS numbers from the base branch
114270
existing_numbers = get_existing_xls_numbers(repo_root)
115-
print(f"Found {len(existing_numbers)} existing XLS numbers.")
271+
print(f"Found {len(existing_numbers)} existing XLS numbers in the repository.")
116272
print(f"Highest existing number: {max(existing_numbers) if existing_numbers else 0}")
117273

118-
# Assign numbers to each draft
119-
next_number = get_next_xls_number(existing_numbers)
274+
all_numbers = existing_numbers | reserved_numbers
275+
120276
assignments = []
121277

278+
if current_pr_assigned is not None:
279+
print(
280+
f"Reusing previously reserved XLS number {current_pr_assigned:04d} for this PR."
281+
)
282+
next_number = current_pr_assigned
283+
else:
284+
next_number = get_next_xls_number(all_numbers)
285+
122286
for draft_dir in sorted(draft_dirs):
123287
assigned_number = next_number
288+
all_numbers.add(assigned_number)
124289
new_dir_name = re.sub(r"^XLS-draft-", f"XLS-{assigned_number:04d}-", draft_dir)
125290
assignments.append({
126291
"draft": draft_dir,
127292
"number": assigned_number,
128293
"new_name": new_dir_name,
129294
})
130-
next_number += 1
295+
# Calculate the next free number for any additional drafts
296+
next_number = get_next_xls_number(all_numbers)
131297

132298
# Output results
133299
print("\n=== XLS Number Assignments ===")

.github/workflows/assign-xls-number.yml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ on:
44
pull_request_target:
55
types: [opened, synchronize, reopened, ready_for_review]
66

7+
concurrency:
8+
group: assign-xls-number-${{ github.repository }}
9+
cancel-in-progress: false
10+
711
jobs:
812
assign-xls-number:
913
runs-on: ubuntu-latest
@@ -76,10 +80,11 @@ jobs:
7680
body-includes: "XLS Number Assignment"
7781

7882
- name: Post or update PR comment
79-
if: steps.check-drafts.outputs.has_drafts == 'true' && steps.find-comment.outputs.comment-id == ''
83+
if: steps.check-drafts.outputs.has_drafts == 'true'
8084
uses: peter-evans/create-or-update-comment@v5
8185
with:
8286
issue-number: ${{ github.event.pull_request.number }}
87+
comment-id: ${{ steps.find-comment.outputs.comment-id }}
8388
body: |
8489
## 🎫 XLS Number Assignment
8590
@@ -99,4 +104,18 @@ jobs:
99104
- Set `status:` to `Draft`
100105
101106
---
102-
*This comment was automatically generated. The XLS number is based on the highest existing number in the repository at the time this PR was opened.*
107+
*This comment was automatically generated. The XLS number below is reserved for this PR based on existing XLS numbers in the repository and other open draft PRs.*
108+
109+
<!-- XLS_NUMBER:${{ steps.assign-number.outputs.xls_number }} -->
110+
111+
- name: Add has-xls-number label
112+
if: steps.check-drafts.outputs.has_drafts == 'true'
113+
env:
114+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
115+
run: |
116+
curl -sS \
117+
-H "Authorization: Bearer $GITHUB_TOKEN" \
118+
-H "Accept: application/vnd.github+json" \
119+
-X POST \
120+
-d '{"labels":["has-xls-number"]}' \
121+
"https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels"

0 commit comments

Comments
 (0)