5555"""
5656import os
5757import sys
58+ import subprocess
5859from argparse import ArgumentParser
5960from collections import OrderedDict
6061from datetime import date , datetime
@@ -77,6 +78,8 @@ LABEL_WONTFIX, LABEL_INVALID, LABEL_DUPLICATE = 'wontfix', 'invalid', 'duplicate
7778LABEL_BUG = 'bug'
7879LABEL_PERFORMANCE , LABEL_SHUFFLE = 'performance' , 'shuffle'
7980LABEL_FEATURE , LABEL_SQL = 'feature request' , 'SQL'
81+ # The release version from which the release branch changes (e.g., branch-YY.MM --> release/YY.MM)
82+ FROM_RELEASE = '25.12'
8083# Queries
8184query_pr = """
8285query ($baseRefName: String!, $after: String) {
@@ -148,7 +151,111 @@ query ($after: String, $since: DateTime) {
148151 }
149152}
150153"""
154+ query_pr_by_commit = """
155+ query ($sha: String!) {
156+ repository(name: "spark-rapids", owner: "NVIDIA") {
157+ commit: object(expression: $sha) {
158+ ... on Commit {
159+ associatedPullRequests(first: 10) {
160+ edges {
161+ node {
162+ title
163+ number
164+ state
165+ url
166+ baseRefName
167+ labels(first: 10) {
168+ nodes {
169+ name
170+ }
171+ }
172+ mergedAt
173+ projectItems(first: 10) {
174+ nodes {
175+ roadmap: fieldValueByName(name: "Roadmap") {
176+ ... on ProjectV2ItemFieldSingleSelectValue {
177+ name
178+ }
179+ }
180+ }
181+ }
182+ }
183+ }
184+ }
185+ }
186+ }
187+ }
188+ }
189+ """
151190
191+ # Get the previous release version string(YY.MM format, 2 months before the current version)
192+ # from the current version, e.g. YY.MM2[current] --> YY.MM1[previous]
193+ # param current_ver: the current version, e.g. YY.MM2
194+ # return: the previous version, e.g. YY.MM1,
195+ def get_prev_release_version (current_ver : str ):
196+ year , month = map (int , current_ver .split ("." ))
197+ if month > 2 :
198+ new_year = year
199+ new_month = month - 2
200+ else :
201+ new_year = year - 1
202+ new_month = month + 10
203+ prev_ver = f"{ new_year :02d} .{ new_month :02d} "
204+ return prev_ver
205+
206+ # Get the commit hashes between two branches or release tags.
207+ # param releases: set of release versions, e.g. {'YY.MM2', 'YY.MM1'}
208+ # return: dict of commit hashes, e.g. {YY.MM2: [sha1, sha2, ...], YY.MM1: [shaX, shaY, ...]}
209+ def get_commits (releases : set ):
210+ rel_list = list (releases )
211+ ver_commits = {}
212+ count = len (rel_list ) # descending version order assured
213+ for i , to_rel in enumerate (rel_list ):
214+ to_branch = f"origin/release/{ to_rel } "
215+ # commits of releases[YY.MM2, YY.MM1] --> git log "YY.MM2..YY.MM1" for YY.MM2, "YY.MM1..YY.MM0" for YY.MM1
216+ if i + 1 < count :
217+ from_rel = rel_list [i + 1 ]
218+ else :
219+ from_rel = get_prev_release_version (to_rel )
220+ based_rel = float (from_rel )
221+ if based_rel < float (FROM_RELEASE ):
222+ from_branch = f"origin/branch-{ from_rel } "
223+ else :
224+ from_branch = f"origin/release/{ from_rel } "
225+
226+ # Get all the commit hashes, excluding those commits whose title contains '[bot]'
227+ git_log_args = [
228+ "git" , "--no-pager" , "log" ,
229+ f"{ from_branch } ..{ to_branch } " , "--pretty=format:%h" ,
230+ "--grep=[bot]" , "-F" , "--invert-grep"
231+ ]
232+
233+ # Use check=True to raise exception if git fails, making errors explicit
234+ result = subprocess .run (git_log_args , capture_output = True , text = True , check = True )
235+
236+ commits = result .stdout .splitlines ()
237+ ver_commits [to_rel ] = commits
238+ return ver_commits
239+
240+ # Get the PR list from commit hashes
241+ # param ver_commits, e.g. {v1: [sha1, sha2, ...], v2: [shaX, shaY, ...]}
242+ # param token: the token for the API
243+ # return: list of PRs associated with the commit hashes, e.g. [{PR1 info}, {PR2 info}, ...]
244+ def get_pr_via_commits (ver_commits : set , token : str ):
245+ pr_list = []
246+ for version , commits in ver_commits .items ():
247+ for sha in commits :
248+ res = post (query = query_pr_by_commit , token = token , variable = {'sha' : sha })
249+ try :
250+ pr_item = res .json ()['data' ]['repository' ]['commit' ]['associatedPullRequests' ]['edges' ][0 ]['node' ]
251+ pr_item ['ver' ] = version
252+ # Handle the case of multiple commits being associated with the same PR
253+ if pr_item not in pr_list and pr_item ['mergedAt' ] is not None :
254+ pr_list .append (pr_item )
255+ except Exception as e :
256+ print (f"Exception: { e } , commit sha '{ sha } ' does not have the associated Pull Request" )
257+ continue
258+ return pr_list
152259
153260def process_changelog (resource_type : str , changelog : dict , releases : set , projects : set , token : str ):
154261 if resource_type == PULL_REQUESTS :
@@ -175,6 +282,11 @@ def process_changelog(resource_type: str, changelog: dict, releases: set, projec
175282 ver = item ["projectItems" ]["nodes" ][0 ]['roadmap' ]['name' ]
176283 project = f"{ RELEASE } { ver } "
177284
285+ # Overwrite project version after the {FROM_RELEASE} if provided
286+ if item .get ('ver' ) is not None :
287+ ver = item ['ver' ]
288+ project = f"{ RELEASE } { ver } "
289+
178290 if not release_project (project , projects ):
179291 continue
180292
@@ -207,11 +319,30 @@ def process_changelog(resource_type: str, changelog: dict, releases: set, projec
207319 })
208320
209321
322+ # Get the PRs based on the release versions
210323def process_pr (releases : set , token : str ):
211324 pr = []
212- for rel in releases :
325+ current_ver = list (releases )[0 ]
326+ current_ver_float = float (current_ver )
327+ based_rel = float (FROM_RELEASE )
328+
329+ # Note: only the last 2 releases are supported/included in the changelog
330+ # Both releases are after {FROM_RELEASE}
331+ if current_ver_float > based_rel :
332+ ver_commits = get_commits (releases )
333+ pr = get_pr_via_commits (ver_commits , token )
334+ # One release is the {FROM_RELEASE}, the other is before the {FROM_RELEASE}
335+ elif current_ver_float == based_rel :
336+ ver_commits = get_commits ({FROM_RELEASE })
337+ pr = get_pr_via_commits (ver_commits , token )
338+ prev_ver = get_prev_release_version (current_ver = FROM_RELEASE )
213339 pr .extend (fetch (resource_type = PULL_REQUESTS , token = token ,
214- variables = {'baseRefName' : f"branch-{ rel } " }))
340+ variables = {'baseRefName' : f"branch-{ prev_ver } " }))
341+ # Both releases are before the {FROM_RELEASE}
342+ else :
343+ for rel in releases :
344+ pr .extend (fetch (resource_type = PULL_REQUESTS , token = token ,
345+ variables = {'baseRefName' : f"branch-{ rel } " }))
215346 return pr
216347
217348
@@ -301,6 +432,8 @@ def main(rels: str, path: str, token: str):
301432 try :
302433 changelog = {} # changelog dict
303434 releases = {x .strip () for x in rels .split (',' )}
435+ # Sort releases in descending order for the follow-up operations
436+ releases = sorted (releases , reverse = True )
304437 projects = {f"{ RELEASE } { rel } " for rel in releases }
305438
306439 print ('Processing pull requests ...' )
0 commit comments