-
Notifications
You must be signed in to change notification settings - Fork 0
/
dependabot_reviews.py
142 lines (120 loc) · 4.18 KB
/
dependabot_reviews.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "rich",
# "furl",
# ]
# ///
import subprocess
import json
from rich.console import Console
from furl import furl
class PullRequest:
def __init__(self, repository: str, title: str, url: str, labels: list[str]):
self.repository: str = repository
self.title: str = title
self._url: furl = furl(url)
self.labels_str: str = self._format_labels(labels)
self.checks_str: str = self._get_and_format_checks()
def __repr__(self) -> str:
return f"PullRequest(repository='{self.repository}', title='{self.title}', url='{self.url}', labels='{self.labels}')"
@property
def url(self) -> str:
return self._url.url
def _format_labels(self, labels: list) -> str:
if labels == []:
return ""
return str("[" + ",".join([label["name"] for label in labels]) + "]")
def _get_and_format_checks(self) -> str:
status_checks = execute_gh_command(
f"pr view {self.url} --json statusCheckRollup"
)["statusCheckRollup"]
if status_checks == []:
return "[yellow] Checks not found[/yellow]"
for check in status_checks:
if check["conclusion"] != "SUCCESS":
return "[red] Checks not passed[/red]"
else:
return "[green] Checks passed[/green]"
def execute_gh_command(command: str, return_response: bool = True) -> dict | None:
"""
Execute a Github CLI command and return the JSON output.
"""
try:
result = subprocess.run(
f"gh {command}",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=True,
)
if return_response:
return json.loads(result.stdout)
except subprocess.CalledProcessError as e:
error_message = f"Command failed with exit code {e.returncode}\n"
error_message += f"stdout: {e.stdout}\n"
error_message += f"stderr: {e.stderr}"
raise RuntimeError(error_message) from e
except json.JSONDecodeError as e:
raise ValueError(
f"Failed to parse JSON output: {e}\nOutput: {result.stdout}"
) from e
def get_pending_dependabot_prs() -> list[PullRequest]:
"""
Return a list of pending dependabot PRs.
"""
response = execute_gh_command(
"search prs --state open --author 'dependabot[bot]' --review-requested @me --json repository,title,url,labels"
)
return [
PullRequest(
repository=pr["repository"]["nameWithOwner"],
title=pr["title"],
url=pr["url"],
labels=pr["labels"],
)
for pr in response
]
def approve_pr(pr: str) -> None:
"""
Approves a PR with the message "@dependabot merge".
"""
execute_gh_command(
f"pr review {pr.url} --approve --body '@dependabot merge'",
return_response=False,
)
def close_pr(pr: PullRequest) -> None:
"""
Closes a PR.
"""
execute_gh_command(f"pr close {pr.url}", return_response=False)
def format_message(pr: PullRequest):
return f"[bold]{pr.repository}[/bold]:{pr.labels_str} '{pr.title}' {pr.url}{pr.checks_str}"
def main() -> None:
terminal = Console()
with terminal.status("Fetching PRs..."):
prs = get_pending_dependabot_prs()
terminal.print(f"Found [red]{len(prs)}[/red] pending dependabot PRs to review")
for pr in prs:
terminal.print(format_message(pr))
terminal.print(
"Approve? ([bold]y[/bold]es/[bold]c[/bold]lose/[bold]N[/bold]o) ",
end="",
)
option = terminal.input().lower().strip()
if option in ["y", "yes"]:
with terminal.status("Approving..."):
approve_pr(pr)
terminal.print("[green][bold]Approved[/]")
elif option in ["c", "close"]:
with terminal.status("Closing..."):
close_pr(pr)
terminal.print("[red][bold]Closed[/]")
else:
terminal.print("[yellow]Skipped[/yellow]")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
exit(0)