11import os
22import sys
3+ from datetime import datetime , timedelta , timezone
34
45import requests
56
@@ -97,6 +98,73 @@ def add_label_to_pr(repo_owner: str, repo_name: str, pr_number: str,
9798 raise e
9899
99100
101+ def get_recent_open_prs (repo_owner : str ,
102+ repo_name : str ,
103+ minutes_back : int = 65 ):
104+ """Get open PRs created or updated in the last N minutes."""
105+ cutoff_time = datetime .now (timezone .utc ) - timedelta (minutes = minutes_back )
106+
107+ url = f"{ GITHUB_API_URL } /repos/{ repo_owner } /{ repo_name } /pulls"
108+ params = {
109+ "state" : "open" ,
110+ "sort" : "updated" ,
111+ "direction" : "desc" ,
112+ "per_page" : 100
113+ }
114+
115+ recent_prs = []
116+ page = 1
117+
118+ try :
119+ while True :
120+ params ["page" ] = page
121+ response = requests .get (url ,
122+ headers = HEADERS ,
123+ params = params ,
124+ timeout = 30 )
125+ response .raise_for_status ()
126+ page_prs = response .json ()
127+
128+ if not page_prs : # no more PRs
129+ break
130+
131+ found_old_pr = False
132+ for pr in page_prs :
133+ created_at = datetime .strptime (
134+ pr ["created_at" ],
135+ "%Y-%m-%dT%H:%M:%SZ" ).replace (tzinfo = timezone .utc )
136+ updated_at = datetime .strptime (
137+ pr ["updated_at" ],
138+ "%Y-%m-%dT%H:%M:%SZ" ).replace (tzinfo = timezone .utc )
139+
140+ if created_at >= cutoff_time or updated_at >= cutoff_time :
141+ recent_prs .append (pr )
142+ else :
143+ # since sorted by updated desc, once we hit an old PR we can stop
144+ found_old_pr = True
145+ break
146+
147+ if found_old_pr :
148+ break
149+
150+ page += 1
151+ # safety limit to avoid infinite loops
152+ if page > 10 : # max 1000 PRs (100 * 10)
153+ print (
154+ f"Warning: Hit pagination limit at page { page } , may have missed some PRs"
155+ )
156+ break
157+
158+ print (
159+ f"Found { len (recent_prs )} PRs created/updated in the last { minutes_back } minutes (checked { page } pages)"
160+ )
161+ return recent_prs
162+
163+ except requests .exceptions .RequestException as e :
164+ print (f"Error fetching PRs: { e } " )
165+ raise
166+
167+
100168def main ():
101169 """
102170 Main function to check user membership and apply community labels.
@@ -106,45 +174,69 @@ def main():
106174 1 - Failed to determine user membership (API permission issues)
107175 2 - Failed to add community label (labeling API issues)
108176 """
109- pr_author = os .environ .get ("PR_AUTHOR" )
110- assert pr_author , "PR_AUTHOR environment variable not set"
111- pr_number = os .environ .get ("PR_NUMBER" )
112- assert pr_number , "PR_NUMBER environment variable not set"
113177 repo_owner = os .environ .get ("REPO_OWNER" )
114178 assert repo_owner , "REPO_OWNER environment variable not set"
115179 repo_name = os .environ .get ("REPO_NAME" )
116180 assert repo_name , "REPO_NAME environment variable not set"
117181 community_label = os .environ .get ("COMMUNITY_LABEL" )
118182 assert community_label , "COMMUNITY_LABEL environment variable not set"
183+ time_window_minutes = int (os .environ .get ("TIME_WINDOW_MINUTES" ))
119184
120185 print (
121- f"Starting NVIDIA membership check for PR author ' { pr_author } ' on PR # { pr_number } ."
186+ f"Starting community PR labeling sweep for { repo_owner } / { repo_name } . Time window: { time_window_minutes } minutes ."
122187 )
123188
124189 try :
125- is_member = check_user_membership ("NVIDIA" , pr_author )
126- except RuntimeError as e :
127- print (
128- f"Critical error during NVIDIA membership check for '{ pr_author } ': { e } "
129- )
130- print ("Halting script due to inability to determine membership status." )
190+ recent_prs = get_recent_open_prs (repo_owner , repo_name ,
191+ time_window_minutes )
192+ except requests .exceptions .RequestException :
193+ print ("Failed to fetch recent PRs" )
131194 sys .exit (1 )
132195
133- print (
134- f"User '{ pr_author } ' is determined to be an NVIDIA member: { is_member } " )
196+ processed_count = 0
197+ labeled_count = 0
198+
199+ for pr in recent_prs :
200+ pr_number = pr ["number" ]
201+ pr_author = pr ["user" ]["login" ]
202+ existing_labels = {label ["name" ] for label in pr ["labels" ]}
203+
204+ if community_label in existing_labels :
205+ print (
206+ f"PR #{ pr_number } by { pr_author } already has community label, skipping"
207+ )
208+ continue
209+
210+ print (f"Processing PR #{ pr_number } by { pr_author } " )
211+ processed_count += 1
135212
136- if not is_member :
137- print (
138- f"User '{ pr_author } ' is a community user. Adding label '{ community_label } '."
139- )
140213 try :
141- add_label_to_pr (repo_owner , repo_name , pr_number , community_label )
142- except requests .exceptions .RequestException as e :
143- print (f"Failed to add community label: { e } " )
144- sys .exit (2 )
145- else :
146- print (
147- f"User '{ pr_author } ' is an NVIDIA member. No label will be added." )
214+ is_member = check_user_membership ("NVIDIA" , pr_author )
215+ except RuntimeError as e :
216+ print (
217+ f"Critical error during NVIDIA membership check for '{ pr_author } ': { e } "
218+ )
219+ print ("Continuing with next PR..." )
220+ continue
221+
222+ if not is_member :
223+ print (
224+ f"User '{ pr_author } ' is a community user. Adding label '{ community_label } '."
225+ )
226+ try :
227+ add_label_to_pr (repo_owner , repo_name , str (pr_number ),
228+ community_label )
229+ labeled_count += 1
230+ except requests .exceptions .RequestException as e :
231+ print (f"Failed to add community label to PR #{ pr_number } : { e } " )
232+ # continue with other PRs instead of exiting
233+ continue
234+ else :
235+ print (f"User '{ pr_author } ' is an NVIDIA member. No label needed." )
236+
237+ print (
238+ f"Sweep complete: processed { processed_count } PRs, labeled { labeled_count } as community"
239+ )
148240
149241
150242if __name__ == "__main__" :
0 commit comments