60 lines
1.5 KiB
Python
Executable File
60 lines
1.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Compute a ``size/{XS,S,M,L,XL}`` label for a PR from its changed files.
|
|
|
|
Reads the GitHub ``pulls/{n}/files`` JSON array on stdin (objects with
|
|
``filename``, ``additions``, ``deletions``) and prints the size label. Lock
|
|
and generated files are excluded so a dependency bump does not inflate the
|
|
size. Pure stdlib so it runs without an install and is unit-tested directly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
# Files whose churn should not count toward review size.
|
|
GENERATED = (
|
|
re.compile(r"^uv\.lock$"),
|
|
re.compile(r"(^|/)package-lock\.json$"),
|
|
re.compile(r"(^|/)yarn\.lock$"),
|
|
)
|
|
|
|
# Upper bound (inclusive) of changed lines for each label, smallest first.
|
|
THRESHOLDS = (
|
|
("XS", 9),
|
|
("S", 49),
|
|
("M", 199),
|
|
("L", 499),
|
|
("XL", float("inf")),
|
|
)
|
|
|
|
|
|
def is_generated(filename: str) -> bool:
|
|
return any(p.search(filename) for p in GENERATED)
|
|
|
|
|
|
def size_label(total: int) -> str:
|
|
for name, upper in THRESHOLDS:
|
|
if total <= upper:
|
|
return f"size/{name}"
|
|
raise AssertionError("THRESHOLDS must end with an unbounded bucket")
|
|
|
|
|
|
def total_changes(files: list[dict]) -> int:
|
|
return sum(
|
|
f.get("additions", 0) + f.get("deletions", 0)
|
|
for f in files
|
|
if not is_generated(f.get("filename", ""))
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
files = json.load(sys.stdin)
|
|
print(size_label(total_changes(files)))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|