Skip to content

Commit cb6bce4

Browse files
akshaykamanzt
andauthored
chore: script to generate bash focus PRs (#5520)
Run `uv run scripts/generate_bash_focus.py <since-tag>` to print a list of all PRs labeled with `bash-focus`. Example output: ``` Using latest tag: 0.14.9 Fetching PRs with label 'bash-focus' since 0.14.9... Found 4 PR(s) with label 'bash-focus': #5515: fix: reuse ports that are available but recently closed Author: @akshayka Link: #5515 Merged: 2025-07-02T15:52:50Z #5507: fix: include all query params in WebSocket connection Author: @harterrt-shopify Link: #5507 Merged: 2025-07-01T17:41:15Z #5491: Ensure cell action tooltip appears above sidebar panel Author: @manzt Link: #5491 Merged: 2025-06-30T18:09:19Z #5474: register UIElement on change if it is missing from the UIElementRegistry Author: @buckley-w-david Link: #5474 Merged: 2025-06-30T15:56:00Z ``` --------- Co-authored-by: Trevor Manz <trevor.j.manz@gmail.com>
1 parent da5e4ec commit cb6bce4

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

scripts/generate_bash_focus.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# /// script
2+
# requires-python = ">=3.13,<3.14"
3+
# dependencies = [
4+
# "msgspec",
5+
# ]
6+
#
7+
# [tool.uv]
8+
# exclude-newer = "2025-06-27T12:38:25.742953-04:00"
9+
# ///
10+
"""Get GitHub PRs labeled with 'bash-focus' since the last release."""
11+
12+
from __future__ import annotations
13+
14+
import re
15+
import subprocess
16+
import sys
17+
18+
import msgspec
19+
20+
21+
class Author(msgspec.Struct):
22+
"""GitHub author/user information."""
23+
24+
login: str
25+
26+
27+
class Label(msgspec.Struct):
28+
"""GitHub label information."""
29+
30+
name: str
31+
color: str | None = None
32+
description: str | None = None
33+
34+
35+
class PullRequest(msgspec.Struct):
36+
"""GitHub Pull Request information."""
37+
38+
number: int
39+
title: str
40+
author: Author
41+
labels: list[Label]
42+
url: str
43+
mergedAt: str | None = None
44+
45+
46+
def get_latest_tag() -> str:
47+
"""Get the latest git tag."""
48+
result = subprocess.run(
49+
["git", "describe", "--tags", "--abbrev=0"],
50+
capture_output=True,
51+
text=True,
52+
check=True,
53+
)
54+
return result.stdout.strip()
55+
56+
57+
def get_commits_since_tag(since_tag: str) -> set[int]:
58+
"""Get PR numbers from commits since a specific tag."""
59+
try:
60+
result = subprocess.run(
61+
[
62+
"git",
63+
"log",
64+
f"{since_tag}..HEAD",
65+
"--format=%s",
66+
"--first-parent",
67+
"main",
68+
],
69+
capture_output=True,
70+
text=True,
71+
check=True,
72+
)
73+
except subprocess.CalledProcessError:
74+
print(f"Error: Tag '{since_tag}' not found. Available tags:")
75+
tag_result = subprocess.run(
76+
["git", "tag", "--list", "--sort=-version:refname"],
77+
capture_output=True,
78+
text=True,
79+
)
80+
for tag in tag_result.stdout.strip().split('\n')[:10]:
81+
print(f" {tag}")
82+
sys.exit(1)
83+
84+
pr_numbers = set()
85+
for line in result.stdout.strip().split('\n'):
86+
if not line:
87+
continue
88+
# Extract PR number from commit message
89+
pr_match = re.search(r'#(\d+)', line)
90+
if pr_match:
91+
pr_numbers.add(int(pr_match.group(1)))
92+
93+
return pr_numbers
94+
95+
96+
def get_prs_with_label(label: str, pr_numbers: set[int]) -> list[PullRequest]:
97+
"""Get PRs with specific label from a set of PR numbers."""
98+
result = subprocess.run(
99+
[
100+
"gh",
101+
"pr",
102+
"list",
103+
"--base",
104+
"main",
105+
"--state",
106+
"merged",
107+
"--label",
108+
label,
109+
"--limit",
110+
"100",
111+
"--json",
112+
"number,title,author,labels,url,mergedAt",
113+
],
114+
check=True,
115+
capture_output=True,
116+
text=True,
117+
)
118+
119+
all_prs = msgspec.json.decode(result.stdout, type=list[PullRequest])
120+
121+
# Filter PRs that are in our commit list
122+
filtered_prs = [pr for pr in all_prs if pr.number in pr_numbers]
123+
124+
return filtered_prs
125+
126+
127+
def main() -> None:
128+
label = "bash-focus"
129+
130+
# Allow specifying a tag or use the latest one
131+
if len(sys.argv) >= 2:
132+
since_tag = sys.argv[1]
133+
else:
134+
since_tag = get_latest_tag()
135+
print(f"Using latest tag: {since_tag}")
136+
137+
print(f"Fetching PRs with label '{label}' since {since_tag}...\n")
138+
139+
pr_numbers = get_commits_since_tag(since_tag)
140+
prs = get_prs_with_label(label, pr_numbers)
141+
142+
if not prs:
143+
print(f"No PRs found with label '{label}' since {since_tag}")
144+
return
145+
146+
print(f"Found {len(prs)} PR(s) with label '{label}':\n")
147+
148+
for pr in prs:
149+
print(f"#{pr.number}: {pr.title}")
150+
print(f" Author: @{pr.author.login}")
151+
print(f" Link: {pr.url}")
152+
print(f" Merged: {pr.mergedAt}")
153+
print()
154+
155+
156+
if __name__ == "__main__":
157+
main()

0 commit comments

Comments
 (0)