mirror of
https://github.com/radareorg/r2sleigh.git
synced 2026-11-08 01:09:18 -10:00
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01456qmqCGpCWJkrg6JUZNNM
53 lines
1.8 KiB
Python
53 lines
1.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Which open pull requests gain or lose `full-ci`: the tip of each stack carries it (ROADMAP D43).
|
|
|
|
Input: a JSON list of open PRs, `[{number, head, base, labels}]` (labels as names or `{name}`).
|
|
Output: `{"label": [numbers], "unlabel": [numbers]}`, sorted. A PR based on the trunk is
|
|
left alone (`plan` runs it in full anyway); a PR from a fork is ignored (`cross: true`).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
LABEL = "full-ci"
|
|
|
|
|
|
def names(labels: list) -> set[str]:
|
|
return {x["name"] if isinstance(x, dict) else x for x in labels}
|
|
|
|
|
|
def plan(prs: list[dict], trunk: str = "master") -> dict[str, list[int]]:
|
|
"""A tip is an open stacked PR whose head no other open PR names as its base. O(n)."""
|
|
own = [p for p in prs if not p.get("cross") and p.get("state", "open").lower() == "open"]
|
|
based_on: dict[str, set[int]] = {}
|
|
for p in own:
|
|
based_on.setdefault(p["base"], set()).add(p["number"])
|
|
label, unlabel = [], []
|
|
for p in own:
|
|
if p["base"] == trunk:
|
|
continue
|
|
has_child = bool(based_on.get(p["head"], set()) - {p["number"]})
|
|
carries = LABEL in names(p.get("labels", []))
|
|
if not has_child and not carries:
|
|
label.append(p["number"])
|
|
elif has_child and carries:
|
|
unlabel.append(p["number"])
|
|
return {"label": sorted(label), "unlabel": sorted(unlabel)}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("prs", help="JSON file of open PRs, or - for stdin")
|
|
parser.add_argument("--trunk", default="master")
|
|
args = parser.parse_args()
|
|
raw = sys.stdin.read() if args.prs == "-" else open(args.prs).read()
|
|
print(json.dumps(plan(json.loads(raw), args.trunk)))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|