@@ -54,24 +54,109 @@ def find_version_boundaries(file_path, pattern, target_version, module=None):
5454 found_ver = match .group (1 )
5555
5656 if found_ver :
57- if found_ver == target_version and not target_release_commit :
57+ if found_ver == target_version :
5858 target_release_commit = commit
5959 break # Stop as soon as we find the target release!
6060
61- if found_ver != target_version and "-SNAPSHOT" not in found_ver :
62- if not prev_version :
63- prev_version = found_ver
64- first_prev_commit = commit
65- elif found_ver != prev_version :
66- # Found a newer stable version before hitting target!
67- prev_version = found_ver
68- first_prev_commit = commit
61+ # Track the first occurrence of the latest stable version before target
62+ if found_ver != target_version and "-SNAPSHOT" not in found_ver and (not prev_version or found_ver != prev_version ):
63+ prev_version = found_ver
64+ first_prev_commit = commit
6965
7066 return first_prev_commit , target_release_commit , prev_version
7167 except SystemExit :
7268 return None , None , None
7369
7470
71+ def verify_commit (commit_hash , directory , module , allowed_versions ):
72+ """Verifies if a commit belongs to the release based on file state."""
73+ if directory == "." :
74+ pom_path = "gapic-libraries-bom/pom.xml"
75+ else :
76+ pom_path = f"{ directory } /pom.xml"
77+
78+ # Check if file exists at that commit to avoid noisy errors
79+ check_cmd = ["git" , "cat-file" , "-e" , f"{ commit_hash } :{ pom_path } " ]
80+ check_result = subprocess .run (check_cmd , stderr = subprocess .PIPE )
81+ if check_result .returncode != 0 :
82+ return False
83+
84+ try :
85+ content = run_cmd (["git" , "show" , f"{ commit_hash } :{ pom_path } " ])
86+ # Allow optional <packaging> tag in between artifactId and version
87+ pattern = re .compile (rf"<artifactId>{ re .escape (module )} </artifactId>\s*(?:<packaging>[^<]+</packaging>\s*)?<version>([^<]+)</version>" , re .DOTALL )
88+
89+ match = pattern .search (content )
90+ if match and match .group (1 ) in allowed_versions :
91+ return True
92+ except SystemExit :
93+ pass
94+
95+ return False
96+
97+
98+ def parse_commit_overrides (commit_data , short_name , prefix_regex , commit_hash , categorize_callback ):
99+ """Parses commit overrides and calls callback for each item."""
100+ match = re .search (r"BEGIN_COMMIT_OVERRIDE(.*?)END_COMMIT_OVERRIDE" , commit_data , re .DOTALL )
101+ if not match :
102+ return False
103+
104+ override_content = match .group (1 )
105+ current_item = []
106+ in_module_item = False
107+
108+ for line in override_content .splitlines ():
109+ line_stripped = line .strip ()
110+ if not line_stripped :
111+ continue
112+
113+ is_new_item = prefix_regex .match (line_stripped )
114+
115+ if is_new_item :
116+ if in_module_item and current_item :
117+ categorize_callback (commit_hash , " " .join (current_item ))
118+ current_item = []
119+ in_module_item = False
120+
121+ should_include = False
122+ if short_name :
123+ if f"[{ short_name } ]" in line_stripped :
124+ should_include = True
125+ else :
126+ should_include = True
127+
128+ if should_include :
129+ in_module_item = True
130+ current_item .append (line_stripped )
131+ elif in_module_item :
132+ if line_stripped .startswith (("PiperOrigin-RevId:" , "Source Link:" )):
133+ continue
134+ if line_stripped in ("END_NESTED_COMMIT" , "BEGIN_NESTED_COMMIT" ):
135+ continue
136+ current_item .append (line_stripped )
137+
138+ if in_module_item and current_item :
139+ categorize_callback (commit_hash , " " .join (current_item ))
140+
141+ return True
142+
143+
144+ def get_tag_or_commit (commit_hash ):
145+ """Returns the tag pointing at the commit if there is exactly one, else the commit hash."""
146+ if not commit_hash :
147+ return None
148+ try :
149+ # Remove ~1 if present to find the actual tag pointing at the commit
150+ clean_hash = commit_hash .split ("~" )[0 ]
151+ tags_output = run_cmd (["git" , "tag" , "--points-at" , clean_hash ])
152+ tags = [line .strip () for line in tags_output .splitlines () if line .strip ()]
153+ if len (tags ) == 1 :
154+ return tags [0 ]
155+ except SystemExit :
156+ pass
157+ return commit_hash
158+
159+
75160def main ():
76161 parser = argparse .ArgumentParser (
77162 description = "Generate release notes based on commit history for a specific module."
@@ -104,7 +189,7 @@ def main():
104189 target_commit = None
105190 if target_release_commit :
106191 target_commit = target_release_commit
107- print (f"Found target release commit at { target_release_commit } . Using exclusive upper boundary { target_commit } " , file = sys .stderr )
192+ print (f"Found target release commit at { target_release_commit } . Using inclusive upper boundary { target_commit } " , file = sys .stderr )
108193
109194 if not target_commit :
110195 print (f"Target version { target_version } not found in history of { pom_path } ." , file = sys .stderr )
@@ -121,7 +206,7 @@ def main():
121206 "git" ,
122207 "log" ,
123208 "--format=%H %s%n%b%n--END_OF_COMMIT--" ,
124- f"{ prev_commit } ..{ target_commit } " if prev_commit else target_commit ,
209+ f"{ prev_commit } ~1 ..{ target_commit } " if prev_commit else target_commit ,
125210 ]
126211 if directory != "." :
127212 notes_cmd .extend (["--" , directory ])
@@ -179,95 +264,30 @@ def categorize_and_append(commit_hash, text):
179264 body = "\n " .join (lines [1 :])
180265
181266 # Verify if commit belongs to this release based on file state
182- should_include = False
183267 target_snapshot = f"{ target_version } -SNAPSHOT"
184268 allowed_versions = (prev_version , target_snapshot ) if prev_version else (target_snapshot ,)
185- try :
186- if directory == "." :
187- pom_path = "gapic-libraries-bom/pom.xml"
188- else :
189- pom_path = f"{ directory } /pom.xml"
190- # Check if file exists at that commit to avoid noisy errors
191- check_cmd = ["git" , "cat-file" , "-e" , f"{ commit_hash } :{ pom_path } " ]
192- check_result = subprocess .run (check_cmd , stderr = subprocess .PIPE )
193- if check_result .returncode == 0 :
194- content = run_cmd (["git" , "show" , f"{ commit_hash } :{ pom_path } " ])
195- if directory == "." :
196- pattern = re .compile (r"<artifactId>gapic-libraries-bom</artifactId>\s*<packaging>pom</packaging>\s*<version>([^<]+)</version>" , re .DOTALL )
197- else :
198- pattern = re .compile (rf"<artifactId>{ re .escape (module )} </artifactId>\s*<version>([^<]+)</version>" , re .DOTALL )
199-
200- match = pattern .search (content )
201-
202-
203- if match and match .group (1 ) in allowed_versions :
204- should_include = True
205- except SystemExit :
206- pass
207-
208- if not should_include :
269+
270+ target_module = "gapic-libraries-bom" if directory == "." else module
271+ if not verify_commit (commit_hash , directory , target_module , allowed_versions ):
209272 continue
210273
211274 # Check for override in the entire message
212275 if "BEGIN_COMMIT_OVERRIDE" in body or "BEGIN_COMMIT_OVERRIDE" in subject :
213- match = re .search (r"BEGIN_COMMIT_OVERRIDE(.*?)END_COMMIT_OVERRIDE" , commit_data , re .DOTALL )
214- if match :
215- override_content = match .group (1 )
216- current_item = []
217- in_module_item = False
218-
219- for line in override_content .splitlines ():
220- line_stripped = line .strip ()
221- if not line_stripped :
222- continue
223-
224- # Check if it's a new item using regex
225- is_new_item = prefix_regex .match (line_stripped )
226-
227- if is_new_item :
228- # If we were in an item, save it
229- if in_module_item and current_item :
230- categorize_and_append (commit_hash , " " .join (current_item ))
231- current_item = []
232- in_module_item = False
233-
234- # Check if this new item is for our module or if we want all
235- should_include = False
236- if args .short_name :
237- if f"[{ args .short_name } ]" in line_stripped :
238- should_include = True
239- else :
240- should_include = True
241-
242- if should_include :
243- in_module_item = True
244- current_item .append (line_stripped )
245- elif in_module_item :
246- # Continuation line
247- if line_stripped .startswith (("PiperOrigin-RevId:" , "Source Link:" )):
248- continue
249- if line_stripped in ("END_NESTED_COMMIT" , "BEGIN_NESTED_COMMIT" ):
250- continue
251- current_item .append (line_stripped )
252-
253- # Save the last item if we were in one
254- if in_module_item and current_item :
255- categorize_and_append (commit_hash , " " .join (current_item ))
256-
257- # Ignore the title since there was an override
258- continue
276+ if parse_commit_overrides (commit_data , args .short_name , prefix_regex , commit_hash , categorize_and_append ):
277+ continue
259278
260279 # Fallback to title check if no override
261280 if prefix_regex .match (subject ):
262281 categorize_and_append (commit_hash , subject )
263282
264283 # Get dates and build header
265- # We use ~1 to be exclusive of the boundary as requested earlier, but let's be careful.
266- # If prev_commit is None, we don't set a range.
267284 target_date = run_cmd (["git" , "log" , "-1" , "--format=%cI" , target_commit ]).strip ()
268285 date_str = target_date .split ("T" )[0 ] # Get YYYY-MM-DD
269286
270- compare_url = f"https://github.com/googleapis/google-cloud-java/compare/{ prev_commit } ...{ target_commit } " if prev_commit else f"https://github.com/googleapis/google-cloud-java/commit/{ target_commit } "
287+ prev_ref = get_tag_or_commit (prev_commit )
288+ target_ref = get_tag_or_commit (target_commit )
289+
290+ compare_url = f"https://github.com/googleapis/google-cloud-java/compare/{ prev_ref } ...{ target_ref } " if prev_ref else f"https://github.com/googleapis/google-cloud-java/commit/{ target_ref } "
271291
272292 print (f"## [{ target_version } ]({ compare_url } ) ({ date_str } )" )
273293 print ()
0 commit comments