github-overlord 0.4.4__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: github-overlord
3
- Version: 0.4.4
3
+ Version: 0.5.0
4
4
  Summary: Automatically merge Dependabot PRs in public repositories
5
5
  Home-page: https://github.com/iloveitaly/github-overlord
6
6
  Keywords: github,dependabot,automation
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Requires-Dist: apscheduler (>=3.10.4,<4.0.0)
14
14
  Requires-Dist: click (>=8.1.7,<9.0.0)
15
15
  Requires-Dist: funcy-pipe (>=0.11.0,<0.12.0)
16
- Requires-Dist: openai (>=1.33.0,<2.0.0)
16
+ Requires-Dist: openai (>=1.35.1,<2.0.0)
17
17
  Requires-Dist: pygithub (>=2.3.0,<3.0.0)
18
18
  Requires-Dist: python-decouple-typed (>=3.11.0,<4.0.0)
19
19
  Requires-Dist: structlog (>=24.2.0,<25.0.0)
@@ -7,8 +7,10 @@ import click
7
7
  import funcy_pipe as fp
8
8
  from github import Github
9
9
  from github.GithubObject import NotSet
10
+ from github.Notification import Notification
10
11
  from github.PullRequest import PullRequest
11
12
 
13
+ from github_overlord.stale_commenter import check_for_stale_comments, is_stale_comment
12
14
  from github_overlord.utils import log
13
15
 
14
16
  AUTOMATIC_MERGE_MESSAGE = "Automatically merged with GitHub overlord"
@@ -154,6 +156,21 @@ def cli():
154
156
  pass
155
157
 
156
158
 
159
+ def extract_repo_reference_from_github_url(url: str):
160
+ """
161
+ Extract the owner and repo from a GitHub URL
162
+ """
163
+
164
+ # convert 'https://github.com/iloveitaly/todoist-digest/pulls' to 'iloveitaly/todoist-digest'
165
+ if "github.com" in url:
166
+ match = re.search(r"github\.com/([^/]+)/([^/]+)", url)
167
+
168
+ if match:
169
+ url = f"{match.group(1)}/{match.group(2)}"
170
+
171
+ return url
172
+
173
+
157
174
  @click.command()
158
175
  @click.option(
159
176
  "--token",
@@ -168,47 +185,50 @@ def dependabot(token, dry_run, repo):
168
185
  Automatically merge dependabot PRs in public repos that have passed CI checks
169
186
  """
170
187
 
171
- if repo:
172
- # convert 'https://github.com/iloveitaly/todoist-digest/pulls' to 'iloveitaly/todoist-digest'
173
- if "github.com" in repo:
174
- match = re.search(r"github\.com/([^/]+)/([^/]+)", repo)
175
-
176
- if match:
177
- repo = f"{match.group(1)}/{match.group(2)}"
178
-
188
+ repo = extract_repo_reference_from_github_url(repo)
179
189
  main(token, dry_run, repo)
180
190
 
181
191
 
182
- def pr_bumper():
183
- """
184
- Look at PRs which you have written:
192
+ @click.command()
193
+ @click.option(
194
+ "--token",
195
+ help="GitHub token, can also be set via GITHUB_TOKEN",
196
+ default=os.getenv("GITHUB_TOKEN"),
197
+ )
198
+ # TODO move this into the parent command
199
+ @click.option("--dry-run", is_flag=True, help="Run script without merging PRs")
200
+ @click.option("--repo", help="Only process a single repository")
201
+ def keep_alive_prs(token, dry_run, repo):
202
+ repo = extract_repo_reference_from_github_url(repo)
185
203
 
186
- 1. There are bots out there which will close the PR if there are is no activity, even if there is no activity from
187
- the maintainer. This will keep the PR open by adding a comment.
188
- 2. PRs that are not merged, been open for at least 30 days, with no comments from the maintainer.
189
- """
204
+ github = Github(token)
205
+ login = github.get_user().login
190
206
 
191
- pass
207
+ github.get_repo(repo).get_pulls(state="open") | fp.filter(
208
+ lambda pr: pr.user.login == login
209
+ ) | fp.map(check_for_stale_comments) | fp.to_list()
192
210
 
193
211
 
194
- """
195
- token = token or os.getenv('GITHUB_TOKEN')
196
- if not token:
197
- raise ValueError("GitHub token must be provided either as an option or via the GITHUB_TOKEN environment variable")
212
+ def notifications(token, dry_run):
213
+ """
214
+ Look at notifications and mark them as read if they are dependabot notifications
215
+ """
198
216
 
199
217
  g = Github(token)
200
218
  user = g.get_user()
201
219
  notifications = user.get_notifications()
202
220
 
203
- for notification in notifications:
204
- print(f"Notification: {notification.subject['title']}, Reason: {notification.reason}")
205
- if not read_only:
206
- notification.mark_as_read()
221
+ def handle_notification(notification: Notification):
222
+ pass
207
223
 
208
- """
224
+ # for notification in notifications:
225
+ # print(f"Notification: {notification.subject['title']}, Reason: {notification.reason}")
226
+ # if not read_only:
227
+ # notification.mark_as_read()
209
228
 
210
229
 
211
230
  cli.add_command(dependabot)
231
+ cli.add_command(keep_alive_prs)
212
232
 
213
233
  if __name__ == "__main__":
214
234
  cli()
@@ -0,0 +1,97 @@
1
+ import json
2
+
3
+ from github.IssueComment import IssueComment
4
+ from github.PullRequest import PullRequest
5
+ from openai import OpenAI
6
+
7
+ from github_overlord.utils import log
8
+
9
+
10
+ def check_for_stale_comments(pr: PullRequest):
11
+ """
12
+ Look at PRs which you have written:
13
+
14
+ 1. There are bots out there which will close the PR if there are is no activity, even if there is no activity from
15
+ the maintainer. This will keep the PR open by adding a comment.
16
+ 2. PRs that are not merged, been open for at least 30 days, with no comments from the maintainer.
17
+
18
+ """
19
+
20
+ log.debug("checking for stale comments", url=pr.html_url)
21
+
22
+ # PR comments are comments on the cod3
23
+ issue = pr.as_issue()
24
+ comments = list(issue.get_comments())
25
+
26
+ if len(comments) == 0:
27
+ return
28
+
29
+ last_comment = comments[-1]
30
+
31
+ # TODO this will need to be changed
32
+ if last_comment.user.login != "github-actions[bot]":
33
+ log.debug("Last comment is not from github-actions[bot]", url=pr.html_url)
34
+ return
35
+
36
+ is_stale, comment = is_stale_comment(last_comment)
37
+
38
+ if not is_stale:
39
+ log.debug("comment does not indicate stale state", url=pr.html_url)
40
+ return
41
+
42
+ log.info(
43
+ "comment indicates stale state, commenting", url=pr.html_url, comment=comment
44
+ )
45
+ # pr.create_issue_comment(comment)
46
+
47
+
48
+ def is_stale_comment(comment: IssueComment):
49
+ """
50
+ Check if the comment indicates that the PR will be automatically closed if there is no activity
51
+ """
52
+
53
+ prompt = """
54
+ A GitHub pull request comment will be included with the author name. Determine if this comment indicates that if there is no activity
55
+ (more commits, comments, etc) the pull request will be closed. If the comment indicates that the pull
56
+ request will be closed, respond with a JSON object like:
57
+
58
+ {
59
+ "stale": "yes",
60
+ "comment": "Friendly reminder on this pull request! Let me know what else may need to be done here."
61
+ }
62
+
63
+ Adjust the comment wording slightly.
64
+
65
+ If the comment does not indicate that the pull request will be closed, respond with:
66
+
67
+ {
68
+ "stale": "no",
69
+ }
70
+ """
71
+ comment_markdown = """
72
+ Author: {comment.user.login}
73
+
74
+ {comment.body}
75
+ """
76
+ client = OpenAI()
77
+
78
+ response = client.chat.completions.create(
79
+ messages=[
80
+ {
81
+ "role": "system",
82
+ "content": prompt,
83
+ },
84
+ {
85
+ "role": "user",
86
+ "content": comment_markdown,
87
+ },
88
+ ],
89
+ model="gpt-3.5-turbo",
90
+ response_format={"type": "json_object"},
91
+ )
92
+
93
+ # TODO got to be a helper for this instead
94
+ message = response.choices[0].message
95
+ response_dict = json.loads(message.content)
96
+
97
+ return (response_dict["stale"] == "yes", response_dict.get("comment"))
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "github-overlord"
3
- version = "0.4.4"
3
+ version = "0.5.0"
4
4
  description = "Automatically merge Dependabot PRs in public repositories"
5
5
  authors = ["Michael Bianco <mike@mikebian.co>"]
6
6
  readme = "README.md"
@@ -15,7 +15,7 @@ python-decouple-typed = "^3.11.0"
15
15
  funcy-pipe = "^0.11.0"
16
16
  structlog = "^24.2.0"
17
17
  apscheduler = "^3.10.4"
18
- openai = "^1.33.0"
18
+ openai = "^1.35.1"
19
19
 
20
20
  [tool.poetry.scripts]
21
21
  github-overlord = "github_overlord:cli"
@@ -29,7 +29,7 @@ float_to_top = true
29
29
  [tool.poetry.group.dev.dependencies]
30
30
  isort = "^5.13.2"
31
31
  black = "^24.4.2"
32
- pyright = "^1.1.366"
32
+ pyright = "^1.1.368"
33
33
  pylint = "^3.2.3"
34
34
 
35
35
  [build-system]