Skip to content

Commit e1c780f

Browse files
committed
create gh actions workflow to notify ext managers
1 parent 77376d8 commit e1c780f

File tree

3 files changed

+462
-0
lines changed

3 files changed

+462
-0
lines changed

.github/generate_codeowners.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import os
2+
import pandas as pd
3+
import re
4+
import requests
5+
import logging
6+
import time
7+
from github import Github
8+
import json
9+
10+
# Set display options
11+
pd.set_option("display.max_columns", None)
12+
pd.set_option("display.max_rows", None)
13+
14+
# Configure the logger
15+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16+
17+
18+
def authenticate_github_session():
19+
"""
20+
Authenticate a GitHub session using a personal access token.
21+
22+
Returns:
23+
A requests.Session object configured with the necessary headers.
24+
"""
25+
gh_session = requests.Session()
26+
27+
if 'GITHUB_TOKEN' in os.environ:
28+
# Use the inbuilt GITHUB_TOKEN for authentication
29+
gh_session.headers.update({'Authorization': f'token {os.environ["GITHUB_TOKEN"]}'})
30+
else:
31+
# Use your personal token for authentication
32+
#gh_session.headers.update({'Authorization': f'token {github_token}'})
33+
os.exit(1)
34+
return gh_session
35+
36+
def get_repository_files(gh_session):
37+
"""
38+
Retrieve the list of files in a GitHub repository.
39+
40+
Args:
41+
gh_session (requests.Session): A GitHub session.
42+
43+
Returns:
44+
list: A list of file metadata.
45+
"""
46+
url = "https://api.github.com/repos/Slicer/ExtensionsIndex/contents"
47+
while True:
48+
response = gh_session.get(url)
49+
if response.status_code == 200:
50+
return response.json()
51+
else:
52+
logging.error("Failed to get repository files. Retrying in 5 minutes...")
53+
time.sleep(300)
54+
55+
def get_contributors(gh_session, github_repo_api):
56+
"""
57+
Get contributors for a GitHub repository.
58+
59+
Args:
60+
gh_session (requests.Session): A GitHub session.
61+
github_repo_api (str): API URL of the GitHub repository.
62+
63+
Returns:
64+
list: A list of contributors.
65+
"""
66+
contributors_url = f"{github_repo_api}/contributors"
67+
while True:
68+
response = gh_session.get(contributors_url)
69+
if response.status_code == 200:
70+
return response.json()
71+
else:
72+
logging.error(f"Failed to get contributors for {github_repo_api}. Retrying in 5 minutes...")
73+
time.sleep(300)
74+
75+
def get_pull_requests(gh_session, github_repo_api):
76+
"""
77+
Get closed pull requests for a GitHub repository.
78+
79+
Args:
80+
gh_session (requests.Session): A GitHub session.
81+
github_repo_api (str): API URL of the GitHub repository.
82+
83+
Returns:
84+
list: A list of closed pull requests.
85+
"""
86+
pull_requests_url = f"{github_repo_api}/pulls?state=closed"
87+
while True:
88+
response = gh_session.get(pull_requests_url)
89+
if response.status_code == 200:
90+
return response.json()
91+
else:
92+
logging.error(f"Failed to get pull requests for {github_repo_api}. Retrying in 5 minutes...")
93+
time.sleep(300)
94+
95+
def get_commit(gh_session, commit_url):
96+
"""
97+
Get commit data from a GitHub commit URL.
98+
99+
Args:
100+
gh_session (requests.Session): A GitHub session.
101+
commit_url (str): URL of the GitHub commit.
102+
103+
Returns:
104+
dict: Commit data.
105+
"""
106+
while True:
107+
response = gh_session.get(commit_url)
108+
if response.status_code == 200:
109+
return response.json()
110+
else:
111+
logging.error(f"Failed to get commit data from {commit_url}. Status code: {response.status_code}. Retrying in 5 minutes...")
112+
time.sleep(300)
113+
114+
def determine_point_of_contact(gh_session, extension_name, extension_file_content):
115+
"""
116+
Determine the point of contact (POC) for a GitHub extension file.
117+
118+
Args:
119+
gh_session (requests.Session): A GitHub session.
120+
extension_name (str): Name of the extension.
121+
extension_file_content (str): Content of the extension file.
122+
123+
Returns:
124+
str: The point of contact (GitHub username) or None if not found.
125+
"""
126+
point_of_contact = None
127+
# Find the scmurl line
128+
scmurl_line = re.search(r'^scmurl.*$', extension_file_content, re.MULTILINE)
129+
130+
if scmurl_line is not None:
131+
# Get the GitHub repo URL
132+
github_repo = scmurl_line.group().split(' ')[1]
133+
134+
# Replace github.com with api.github.com/repos in the URL
135+
github_repo_api = github_repo.replace('github.com', 'api.github.com/repos')
136+
137+
# Remove .git from the end of the URL if it's present
138+
if github_repo_api.endswith('.git'):
139+
github_repo_api = github_repo_api[:-4]
140+
141+
# Check if it's not another repository
142+
if "github.com" in github_repo:
143+
contributors = get_contributors(gh_session, github_repo_api)
144+
pull_requests = get_pull_requests(gh_session, github_repo_api)
145+
146+
# Check if there is only one contributor
147+
if len(contributors) == 1 or len(pull_requests) == 0:
148+
point_of_contact = contributors[0]['login']
149+
logging.info("Found number of contributors: " + str(len(contributors)))
150+
logging.info("Point of contact: " + point_of_contact)
151+
else:
152+
# Find the closed pull requests for the repository
153+
if pull_requests:
154+
# Find the latest closed pull request
155+
latest_pull_request = pull_requests[0]
156+
latest_pull_request_number = latest_pull_request['number']
157+
if latest_pull_request['merge_commit_sha'] is not None:
158+
merge_commit_sha = latest_pull_request['merge_commit_sha']
159+
160+
# Get the merge commit for the latest closed pull request
161+
commit_url = f"{github_repo_api}/commits/{merge_commit_sha}"
162+
commit = get_commit(gh_session, commit_url)
163+
committer = commit['committer']['login']
164+
if commit['committer']['login'] == 'web-flow':
165+
if commit['author'] is not None:
166+
committer = commit['author']['login']
167+
else:
168+
committer = None
169+
point_of_contact = committer
170+
171+
return point_of_contact
172+
173+
def process_extensions():
174+
"""
175+
Process GitHub extension files, determine the point of contact for each extension, and return the data as a DataFrame.
176+
177+
Returns:
178+
pandas.DataFrame: A DataFrame containing extension data with ExtensionName, PointOfContact, and ExtensionPath.
179+
"""
180+
gh_session = authenticate_github_session()
181+
extensions_data = []
182+
183+
files = get_repository_files(gh_session)
184+
185+
for file_meta_data in files:
186+
if file_meta_data['name'].endswith('.s4ext'):
187+
extension_path = file_meta_data['html_url'].split('/blob/main')[1]
188+
extension_name = file_meta_data['name'][:-6]
189+
logging.info("Processing extension: " + extension_name)
190+
# Get the content of the .s4ext file
191+
extension_file_content = gh_session.get(file_meta_data['download_url']).text
192+
point_of_contact = determine_point_of_contact(gh_session, extension_name, extension_file_content)
193+
extensions_data.append({'ExtensionName': extension_name, 'PointOfContact': point_of_contact, 'ExtensionPath': extension_path})
194+
195+
df = pd.DataFrame(extensions_data)
196+
return df
197+
198+
result_df = process_extensions()
199+
200+
def generate_codeowners_file(extension_data, output_file="CODEOWNERS"):
201+
"""
202+
Generate a CODEOWNERS file using the extension data and write it to a specified output file.
203+
204+
Args:
205+
extension_data (pandas.DataFrame): A DataFrame containing extension data.
206+
output_file (str): The name of the output CODEOWNERS file.
207+
"""
208+
with open(output_file, 'w') as codeowners_file:
209+
for index, row in extension_data.iterrows():
210+
point_of_contact = row['PointOfContact']
211+
if point_of_contact is not None:
212+
codeowners_file.write(f"{row['ExtensionPath']} {'@' + point_of_contact}\n")
213+
214+
if __name__ == "__main__":
215+
216+
generate_codeowners_file(result_df, output_file="NEW_CODEOWNERS")

0 commit comments

Comments
 (0)