github-overlord 0.3.2__tar.gz → 0.4.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.3.2
3
+ Version: 0.4.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,6 +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
17
  Requires-Dist: pygithub (>=2.3.0,<3.0.0)
17
18
  Requires-Dist: python-decouple-typed (>=3.11.0,<4.0.0)
18
19
  Requires-Dist: structlog (>=24.2.0,<25.0.0)
@@ -47,3 +48,7 @@ Options:
47
48
  ```
48
49
 
49
50
  ### Docker Cron
51
+
52
+ There's a docker container you can use to run this on a cron. [Fits nicely into a orange pi.](https://mikebian.co/pi-hole-tailscale-and-docker-on-an-orange-pi/)
53
+
54
+ Check out [docker-compose.yml](./docker-compose.yml) for an example, or `git pull ghcr.io/iloveitaly/github-overlord:latest`.
@@ -26,4 +26,8 @@ Options:
26
26
  --help Show this message and exit.
27
27
  ```
28
28
 
29
- ### Docker Cron
29
+ ### Docker Cron
30
+
31
+ There's a docker container you can use to run this on a cron. [Fits nicely into a orange pi.](https://mikebian.co/pi-hole-tailscale-and-docker-on-an-orange-pi/)
32
+
33
+ Check out [docker-compose.yml](./docker-compose.yml) for an example, or `git pull ghcr.io/iloveitaly/github-overlord:latest`.
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import re
3
3
  import time
4
+ from types import NoneType
4
5
 
5
6
  import click
6
7
  import funcy_pipe as fp
@@ -42,13 +43,39 @@ def resolve_async_status(object, key):
42
43
  return
43
44
 
44
45
 
46
+ def handle_stale_dependabot_pr(pr: PullRequest) -> None:
47
+ """
48
+ Handle a dependabot PR that has been open for at least 30 days and has conflicts
49
+ """
50
+
51
+ assert not pr.mergeable
52
+ assert pr.mergeable_state == "dirty"
53
+
54
+ if pr.body is None:
55
+ return
56
+
57
+ if "Automatic rebases have been disabled on this pull request" in pr.body:
58
+ log.info(
59
+ "PR has disabled automatic rebases, manually commenting", url=pr.html_url
60
+ )
61
+
62
+ pr.create_issue_comment("@dependabot rebase")
63
+
64
+
45
65
  def is_eligible_for_merge(pr: PullRequest):
46
66
  resolve_async_status(pr, "mergeable")
47
67
 
68
+ if pr.state == "closed":
69
+ log.debug("PR is closed", url=pr.html_url)
70
+ return
71
+
48
72
  if not pr.mergeable:
73
+ log.debug("PR is not mergeable", url=pr.html_url)
74
+ handle_stale_dependabot_pr(pr)
49
75
  return False
50
76
 
51
77
  if pr.user.login != "dependabot[bot]":
78
+ log.debug("PR is not from dependabot", url=pr.html_url)
52
79
  return False
53
80
 
54
81
  last_commit = pr.get_commits().reversed[0]
@@ -57,43 +84,51 @@ def is_eligible_for_merge(pr: PullRequest):
57
84
 
58
85
  # status is different than CI runs!
59
86
  if len(combined_status.statuses) > 0 and status != "success":
87
+ log.debug("PR has failed status", url=pr.html_url, status=status)
60
88
  return False
61
89
 
62
90
  # checks are the CI runs
63
91
  all_checks_successful = (
64
- last_commit.get_check_runs() | fp.pluck_attr("conclusion") | fp.all("success")
92
+ last_commit.get_check_runs()
93
+ | fp.pluck_attr("conclusion")
94
+ | fp.all({"success", "skipped"})
65
95
  )
66
96
 
67
97
  if not all_checks_successful:
98
+ log.debug("PR has failed checks", url=pr.html_url)
68
99
  return False
69
100
 
70
101
  return True
71
102
 
72
103
 
73
104
  def process_repo(repo, dry_run):
74
- log.debug("checking repository", repo=repo.full_name)
105
+ with log.context(repo=repo.full_name):
106
+ log.debug("checking repository")
75
107
 
76
- if repo.fork:
77
- log.debug("skipping forked repo")
78
- return
108
+ if repo.fork:
109
+ log.debug("skipping forked repo")
110
+ return
79
111
 
80
- pulls = repo.get_pulls(state="open")
112
+ pulls = repo.get_pulls(state="open")
81
113
 
82
- if pulls.totalCount == 0:
83
- log.debug("no open prs, skipping")
84
- return
114
+ if pulls.totalCount == 0 or pulls == NoneType:
115
+ log.debug("no open prs, skipping")
116
+ return
85
117
 
86
- merged_pr_count = 0
118
+ merged_pr_count = 0
87
119
 
88
- for pr in pulls:
89
- if is_eligible_for_merge(pr):
90
- merge_pr(pr, dry_run)
120
+ for pr in pulls:
121
+ if is_eligible_for_merge(pr):
122
+ merge_pr(pr, dry_run)
91
123
 
92
- merged_pr_count += 1
93
- else:
94
- log.debug("skipping PR", url=pr.html_url)
124
+ merged_pr_count += 1
125
+ else:
126
+ log.debug("skipping PR", url=pr.html_url)
95
127
 
96
- log.info("merged prs", count=merged_pr_count)
128
+ if merged_pr_count == 0:
129
+ log.debug("no PRs were merged")
130
+ else:
131
+ log.info("merged prs", count=merged_pr_count)
97
132
 
98
133
 
99
134
  def main(token, dry_run, repo):
@@ -145,9 +180,34 @@ def dependabot(token, dry_run, repo):
145
180
 
146
181
 
147
182
  def pr_bumper():
183
+ """
184
+ Look at PRs which you have written:
185
+
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
+ """
190
+
148
191
  pass
149
192
 
150
193
 
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")
198
+
199
+ g = Github(token)
200
+ user = g.get_user()
201
+ notifications = user.get_notifications()
202
+
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()
207
+
208
+ """
209
+
210
+
151
211
  cli.add_command(dependabot)
152
212
 
153
213
  if __name__ == "__main__":
@@ -64,7 +64,7 @@ def setup():
64
64
 
65
65
  configure_logger()
66
66
 
67
- log.info("application setup")
67
+ log.debug("application setup")
68
68
 
69
69
  # local state in a method is strange, but it works :/
70
70
  setup.complete = True
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "github-overlord"
3
- version = "0.3.2"
3
+ version = "0.4.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,6 +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
19
 
19
20
  [tool.poetry.scripts]
20
21
  github-overlord = "github_overlord:cli"