@@ -47,15 +47,18 @@ def main(): # pragma: no cover
47
47
"ORGANIZATION environment variable not set, searching all repos owned by token owner"
48
48
)
49
49
50
+ # Fetch additional metrics configuration
51
+ additional_metrics = os .getenv ("ADDITIONAL_METRICS" , "" ).split ("," )
52
+
50
53
# Iterate over repos in the org, acquire inactive days,
51
54
# and print out the repo url and days inactive if it's over the threshold (inactive_days)
52
55
inactive_repos = get_inactive_repos (
53
- github_connection , inactive_days_threshold , organization
56
+ github_connection , inactive_days_threshold , organization , additional_metrics
54
57
)
55
58
56
59
if inactive_repos :
57
60
output_to_json (inactive_repos )
58
- write_to_markdown (inactive_repos , inactive_days_threshold )
61
+ write_to_markdown (inactive_repos , inactive_days_threshold , additional_metrics )
59
62
else :
60
63
print ("No stale repos found" )
61
64
@@ -91,14 +94,17 @@ def is_repo_exempt(repo, exempt_repos, exempt_topics):
91
94
return False
92
95
93
96
94
- def get_inactive_repos (github_connection , inactive_days_threshold , organization ):
97
+ def get_inactive_repos (
98
+ github_connection , inactive_days_threshold , organization , additional_metrics = None
99
+ ):
95
100
"""Return and print out the repo url and days inactive if it's over
96
101
the threshold (inactive_days).
97
102
98
103
Args:
99
104
github_connection: The GitHub connection object.
100
105
inactive_days_threshold: The threshold (in days) for considering a repo as inactive.
101
106
organization: The name of the organization to retrieve repositories from.
107
+ additional_metrics: A list of additional metrics to include in the report.
102
108
103
109
Returns:
104
110
A list of tuples containing the repo, days inactive, the date of the last push and
@@ -137,17 +143,49 @@ def get_inactive_repos(github_connection, inactive_days_threshold, organization)
137
143
days_inactive = (datetime .now (timezone .utc ) - active_date ).days
138
144
visibility = "private" if repo .private else "public"
139
145
if days_inactive > int (inactive_days_threshold ):
140
- inactive_repos . append (
141
- ( repo . html_url , days_inactive , active_date_disp , visibility )
146
+ repo_data = set_repo_data (
147
+ repo , days_inactive , active_date_disp , visibility , additional_metrics
142
148
)
143
- print ( f" { repo . html_url } : { days_inactive } days inactive" ) # type: ignore
149
+ inactive_repos . append ( repo_data )
144
150
if organization :
145
151
print (f"Found { len (inactive_repos )} stale repos in { organization } " )
146
152
else :
147
153
print (f"Found { len (inactive_repos )} stale repos" )
148
154
return inactive_repos
149
155
150
156
157
+ def get_days_since_last_release (repo ):
158
+ """Get the number of days since the last release of the repository.
159
+
160
+ Args:
161
+ repo: A Github repository object.
162
+
163
+ Returns:
164
+ The number of days since the last release.
165
+ """
166
+ try :
167
+ last_release = next (repo .releases ())
168
+ return (datetime .now (timezone .utc ) - last_release .created_at ).days
169
+ except StopIteration :
170
+ return None
171
+
172
+
173
+ def get_days_since_last_pr (repo ):
174
+ """Get the number of days since the last pull request was made in the repository.
175
+
176
+ Args:
177
+ repo: A Github repository object.
178
+
179
+ Returns:
180
+ The number of days since the last pull request was made.
181
+ """
182
+ try :
183
+ last_pr = next (repo .pull_requests (state = "all" ))
184
+ return (datetime .now (timezone .utc ) - last_pr .created_at ).days
185
+ except StopIteration :
186
+ return None
187
+
188
+
151
189
def get_active_date (repo ):
152
190
"""Get the last activity date of the repository.
153
191
@@ -180,41 +218,68 @@ def get_active_date(repo):
180
218
return active_date
181
219
182
220
183
- def write_to_markdown (inactive_repos , inactive_days_threshold , file = None ):
221
+ def write_to_markdown (
222
+ inactive_repos , inactive_days_threshold , additional_metrics = None , file = None
223
+ ):
184
224
"""Write the list of inactive repos to a markdown file.
185
225
186
226
Args:
187
- inactive_repos: A list of tuples containing the repo, days inactive,
188
- the date of the last push, and repository visibility (public/private).
227
+ inactive_repos: A list of dictionaries containing the repo, days inactive,
228
+ the date of the last push, repository visibility (public/private),
229
+ days since the last release, and days since the last pr
189
230
inactive_days_threshold: The threshold (in days) for considering a repo as inactive.
231
+ additional_metrics: A list of additional metrics to include in the report.
190
232
file: A file object to write to. If None, a new file will be created.
191
233
192
234
"""
193
- inactive_repos .sort (key = lambda x : x [1 ], reverse = True )
235
+ inactive_repos = sorted (
236
+ inactive_repos , key = lambda x : x ["days_inactive" ], reverse = True
237
+ )
194
238
with file or open ("stale_repos.md" , "w" , encoding = "utf-8" ) as markdown_file :
195
239
markdown_file .write ("# Inactive Repositories\n \n " )
196
240
markdown_file .write (
197
241
f"The following repos have not had a push event for more than "
198
242
f"{ inactive_days_threshold } days:\n \n "
199
243
)
200
244
markdown_file .write (
201
- "| Repository URL | Days Inactive | Last Push Date | Visibility |\n "
245
+ "| Repository URL | Days Inactive | Last Push Date | Visibility |"
202
246
)
203
- markdown_file .write ("| --- | --- | --- | ---: |\n " )
204
- for repo_url , days_inactive , last_push_date , visibility in inactive_repos :
247
+ # Include additional metrics columns if configured
248
+ if additional_metrics :
249
+ if "release" in additional_metrics :
250
+ markdown_file .write (" Days Since Last Release |" )
251
+ if "pr" in additional_metrics :
252
+ markdown_file .write (" Days Since Last PR |" )
253
+ markdown_file .write ("\n | --- | --- | --- | ---: |" )
254
+ if additional_metrics and (
255
+ "release" in additional_metrics or "pr" in additional_metrics
256
+ ):
257
+ markdown_file .write (" ---: |" )
258
+ markdown_file .write ("\n " )
259
+ for repo_data in inactive_repos :
205
260
markdown_file .write (
206
- f"| { repo_url } | { days_inactive } | { last_push_date } | { visibility } |\n "
261
+ f"| { repo_data ['url' ]} \
262
+ | { repo_data ['days_inactive' ]} \
263
+ | { repo_data ['last_push_date' ]} \
264
+ | { repo_data ['visibility' ]} |"
207
265
)
266
+ if additional_metrics :
267
+ if "release" in additional_metrics :
268
+ markdown_file .write (f" { repo_data ['days_since_last_release' ]} |" )
269
+ if "pr" in additional_metrics :
270
+ markdown_file .write (f" { repo_data ['days_since_last_pr' ]} |" )
271
+ markdown_file .write ("\n " )
208
272
print ("Wrote stale repos to stale_repos.md" )
209
273
210
274
211
275
def output_to_json (inactive_repos , file = None ):
212
276
"""Convert the list of inactive repos to a json string.
213
277
214
278
Args:
215
- inactive_repos: A list of tuples containing the repo,
216
- days inactive, the date of the last push, and
217
- visiblity of the repository (public/private).
279
+ inactive_repos: A list of dictionaries containing the repo,
280
+ days inactive, the date of the last push,
281
+ visiblity of the repository (public/private),
282
+ days since the last release, and days since the last pr.
218
283
219
284
Returns:
220
285
JSON formatted string of the list of inactive repos.
@@ -226,18 +291,23 @@ def output_to_json(inactive_repos, file=None):
226
291
# "url": "https://github.com/owner/repo",
227
292
# "daysInactive": 366,
228
293
# "lastPushDate": "2020-01-01"
294
+ # "daysSinceLastRelease": "5"
295
+ # "daysSinceLastPR": "10"
229
296
# }
230
297
# ]
231
298
inactive_repos_json = []
232
- for repo_url , days_inactive , last_push_date , visibility in inactive_repos :
233
- inactive_repos_json .append (
234
- {
235
- "url" : repo_url ,
236
- "daysInactive" : days_inactive ,
237
- "lastPushDate" : last_push_date ,
238
- "visibility" : visibility ,
239
- }
240
- )
299
+ for repo_data in inactive_repos :
300
+ repo_json = {
301
+ "url" : repo_data ["url" ],
302
+ "daysInactive" : repo_data ["days_inactive" ],
303
+ "lastPushDate" : repo_data ["last_push_date" ],
304
+ "visibility" : repo_data ["visibility" ],
305
+ }
306
+ if "release" in repo_data :
307
+ repo_json ["daysSinceLastRelease" ] = repo_data ["days_since_last_release" ]
308
+ if "pr" in repo_data :
309
+ repo_json ["daysSinceLastPR" ] = repo_data ["days_since_last_pr" ]
310
+ inactive_repos_json .append (repo_json )
241
311
inactive_repos_json = json .dumps (inactive_repos_json )
242
312
243
313
# add output to github action output
@@ -298,5 +368,41 @@ def auth_to_github():
298
368
return github_connection # type: ignore
299
369
300
370
371
+ def set_repo_data (
372
+ repo , days_inactive , active_date_disp , visibility , additional_metrics
373
+ ):
374
+ """
375
+ Constructs a dictionary with repository data
376
+ including optional metrics based on additional metrics specified.
377
+
378
+ Args:
379
+ repo: The repository object.
380
+ days_inactive: Number of days the repository has been inactive.
381
+ active_date_disp: The display string of the last active date.
382
+ visibility: The visibility status of the repository (e.g., private or public).
383
+ additional_metrics: A list of strings indicating which additional metrics to include.
384
+
385
+ Returns:
386
+ A dictionary with the repository data.
387
+ """
388
+ repo_data = {
389
+ "url" : repo .html_url ,
390
+ "days_inactive" : days_inactive ,
391
+ "last_push_date" : active_date_disp ,
392
+ "visibility" : visibility ,
393
+ }
394
+ # Fetch and include additional metrics if configured
395
+ repo_data ["days_since_last_release" ] = None
396
+ repo_data ["days_since_last_pr" ] = None
397
+ if additional_metrics :
398
+ if "release" in additional_metrics :
399
+ repo_data ["days_since_last_release" ] = get_days_since_last_release (repo )
400
+ if "pr" in additional_metrics :
401
+ repo_data ["days_since_last_pr" ] = get_days_since_last_pr (repo )
402
+
403
+ print (f"{ repo .html_url } : { days_inactive } days inactive" ) # type: ignore
404
+ return repo_data
405
+
406
+
301
407
if __name__ == "__main__" :
302
408
main ()
0 commit comments