@@ -16,6 +16,56 @@ def run_cmd(cmd, cwd=None):
1616 return result .stdout
1717
1818
19+ def find_version_boundaries (file_path , pattern , target_version , module = None ):
20+ """Scans history of a file to find release boundaries moving forward."""
21+ log_cmd = [
22+ "git" ,
23+ "log" ,
24+ "--oneline" ,
25+ "--all" ,
26+ "--" ,
27+ file_path ,
28+ ]
29+ try :
30+ log_output = run_cmd (log_cmd )
31+ commits = [line .split ()[0 ] for line in log_output .splitlines () if line ]
32+ commits .reverse () # Move forward in time!
33+
34+ first_prev_commit = None
35+ target_release_commit = None
36+ prev_version = None
37+
38+ for commit in commits :
39+ show_cmd = ["git" , "show" , f"{ commit } :{ file_path } " ]
40+ try :
41+ content = run_cmd (show_cmd )
42+ except SystemExit :
43+ continue
44+
45+ found_ver = None
46+ match = pattern .search (content )
47+ if match :
48+ found_ver = match .group (1 )
49+
50+ if found_ver :
51+ if found_ver == target_version and not target_release_commit :
52+ target_release_commit = commit
53+ break # Stop as soon as we find the target release!
54+
55+ if found_ver != target_version and "-SNAPSHOT" not in found_ver :
56+ if not prev_version :
57+ prev_version = found_ver
58+ first_prev_commit = commit
59+ elif found_ver != prev_version :
60+ # Found a newer stable version before hitting target!
61+ prev_version = found_ver
62+ first_prev_commit = commit
63+
64+ return first_prev_commit , target_release_commit , prev_version
65+ except SystemExit :
66+ return None , None , None
67+
68+
1969def main ():
2070 parser = argparse .ArgumentParser (
2171 description = "Generate release notes based on commit history for a specific module."
@@ -36,136 +86,24 @@ def main():
3686 directory = args .directory
3787 target_version = args .version
3888
39- # 1. Scan backwards through git history of versions.txt
40- # We use -G to find commits that modified lines matching the module name.
41- log_cmd = [
42- "git" ,
43- "log" ,
44- "--oneline" ,
45- f"-G^{ re .escape (module )} :" ,
46- "--" ,
47- "versions.txt" ,
48- ]
49- log_output = run_cmd (log_cmd )
50-
51- commits = [line .split ()[0 ] for line in log_output .splitlines () if line ]
52-
89+ # 1. Scan history of pom.xml
90+ if directory == "." :
91+ pom_path = "gapic-libraries-bom/pom.xml"
92+ else :
93+ pom_path = f"{ directory } /pom.xml"
94+ pom_pattern = re .compile (r"<version>([^<]+)</version>" )
95+
96+ prev_commit , target_release_commit , prev_version = find_version_boundaries (pom_path , pom_pattern , target_version )
97+
5398 target_commit = None
54- prev_commit = None
55- prev_version = None
56-
57- for commit in commits :
58- # Get content of versions.txt at this commit
59- show_cmd = ["git" , "show" , f"{ commit } :versions.txt" ]
60- try :
61- content = run_cmd (show_cmd )
62- except SystemExit :
63- continue # Ignore errors if file couldn't be read
64-
65- # Find the line for the module
66- pattern = re .compile (rf"^{ re .escape (module )} :([^:]+):([^:]+)$" )
67- for line in content .splitlines ():
68- match = pattern .match (line )
69- if match :
70- released_ver = match .group (1 )
71- current_ver = match .group (2 )
72-
73- # Condition for target version
74- if released_ver == target_version and not target_commit :
75- target_commit = commit
76- print (f"Found target version { target_version } at { commit } " , file = sys .stderr )
77-
78- # Condition for previous non-snapshot version
79- # We ignore snapshot versions by checking both fields.
80- elif (
81- target_commit
82- and released_ver != target_version
83- and "-SNAPSHOT" not in released_ver
84- and "-SNAPSHOT" not in current_ver
85- ):
86- prev_commit = commit
87- prev_version = released_ver
88- print (f"Found previous version { released_ver } at { commit } " , file = sys .stderr )
89- break
90- if prev_commit :
91- break
92-
99+ if target_release_commit :
100+ target_commit = target_release_commit
101+ print (f"Found target release commit at { target_release_commit } . Using exclusive upper boundary { target_commit } " , file = sys .stderr )
102+
93103 if not target_commit :
94- print (
95- f"Target version { target_version } not found in history for module { module } ."
96- )
104+ print (f"Target version { target_version } not found in history of { pom_path } ." , file = sys .stderr )
97105 sys .exit (1 )
98106
99- # Fallback for initial version if no previous version found
100- if not prev_commit :
101- print (
102- f"Previous version not found in history of versions.txt for module { module } . Trying pom.xml history..." , file = sys .stderr
103- )
104-
105- pom_path = f"{ directory } /pom.xml"
106- pom_log_cmd = [
107- "git" ,
108- "log" ,
109- "--oneline" ,
110- "--" ,
111- pom_path ,
112- ]
113- try :
114- pom_log_output = run_cmd (pom_log_cmd )
115- pom_commits = [line .split ()[0 ] for line in pom_log_output .splitlines () if line ]
116-
117- prev_commit_in_loop = None
118- target_end_commit = None
119- prev_start_commit = None
120-
121- for commit in pom_commits :
122- show_cmd = ["git" , "show" , f"{ commit } :{ pom_path } " ]
123- try :
124- content = run_cmd (show_cmd )
125- except SystemExit :
126- continue
127-
128- match = re .search (r"<version>([^<]+)</version>" , content )
129- if match :
130- ver = match .group (1 )
131-
132- if ver == target_version and not target_end_commit :
133- # Moving backwards, this is the first commit with target version!
134- # The previous commit in loop was the one that changed it AWAY from target version!
135- target_end_commit = prev_commit_in_loop
136- print (
137- f"Found commit changing away from { target_version } at { target_end_commit } " ,
138- file = sys .stderr ,
139- )
140-
141- elif (
142- target_end_commit
143- and ver != target_version
144- and "-SNAPSHOT" not in ver
145- ):
146- # This is the commit where the previous stable version was set!
147- prev_start_commit = commit
148- print (
149- f"Found previous stable version { ver } at { commit } " ,
150- file = sys .stderr ,
151- )
152- break
153-
154- prev_commit_in_loop = commit
155-
156- if prev_start_commit and target_end_commit :
157- prev_commit = prev_start_commit
158- # Use W~1 to be exclusive of W (the commit that changed it away)
159- target_commit = f"{ target_end_commit } ~1"
160- print (f"Using range derived from pom.xml: { prev_commit } ..{ target_commit } " , file = sys .stderr )
161- else :
162- print (f"Could not find complete range in pom.xml. Falling back to initial release logic." , file = sys .stderr )
163- prev_commit = None
164-
165- except SystemExit :
166- print (f"Failed to read pom.xml history." , file = sys .stderr )
167- prev_commit = None
168-
169107 range_desc = f"between { prev_commit } and { target_commit } " if prev_commit else f"up to { target_commit } "
170108 print (
171109 f"Generating notes { range_desc } for directory { directory } " , file = sys .stderr
@@ -178,11 +116,13 @@ def main():
178116 "log" ,
179117 "--format=%H %s%n%b%n--END_OF_COMMIT--" ,
180118 f"{ prev_commit } ..{ target_commit } " if prev_commit else target_commit ,
181- "--" ,
182- directory ,
183119 ]
120+ if directory != "." :
121+ notes_cmd .extend (["--" , directory ])
184122 notes_output = run_cmd (notes_cmd )
185123
124+
125+
186126 # Filter commit titles based on allowed prefixes and categorize them
187127 # Supports scopes in parentheses, e.g., feat(spanner):
188128 prefix_regex = re .compile (r"^(feat|fix|deps|docs|chore\(deps\)|build\(deps\))(\([^)]+\))?(!)?:" )
@@ -232,6 +172,36 @@ def categorize_and_append(commit_hash, text):
232172
233173 body = "\n " .join (lines [1 :])
234174
175+ # Verify if commit belongs to this release based on file state
176+ should_include = False
177+ target_snapshot = f"{ target_version } -SNAPSHOT"
178+ allowed_versions = (prev_version , target_snapshot ) if prev_version else (target_snapshot ,)
179+ try :
180+ if directory == "." :
181+ pom_path = "gapic-libraries-bom/pom.xml"
182+ else :
183+ pom_path = f"{ directory } /pom.xml"
184+ # Check if file exists at that commit to avoid noisy errors
185+ check_cmd = ["git" , "cat-file" , "-e" , f"{ commit_hash } :{ pom_path } " ]
186+ check_result = subprocess .run (check_cmd , stderr = subprocess .PIPE )
187+ if check_result .returncode == 0 :
188+ content = run_cmd (["git" , "show" , f"{ commit_hash } :{ pom_path } " ])
189+ if directory == "." :
190+ pattern = re .compile (r"<artifactId>gapic-libraries-bom</artifactId>\s*<packaging>pom</packaging>\s*<version>([^<]+)</version>" , re .DOTALL )
191+ else :
192+ pattern = re .compile (rf"<artifactId>{ re .escape (module )} </artifactId>\s*<version>([^<]+)</version>" , re .DOTALL )
193+
194+ match = pattern .search (content )
195+
196+
197+ if match and match .group (1 ) in allowed_versions :
198+ should_include = True
199+ except SystemExit :
200+ pass
201+
202+ if not should_include :
203+ continue
204+
235205 # Check for override in the entire message
236206 if "BEGIN_COMMIT_OVERRIDE" in body or "BEGIN_COMMIT_OVERRIDE" in subject :
237207 match = re .search (r"BEGIN_COMMIT_OVERRIDE(.*?)END_COMMIT_OVERRIDE" , commit_data , re .DOTALL )
@@ -296,35 +266,38 @@ def categorize_and_append(commit_hash, text):
296266 print (f"## [{ target_version } ]({ compare_url } ) ({ date_str } )" )
297267 print ()
298268
299- if breaking_changes :
300- print ("### ⚠ BREAKING CHANGES\n " )
301- for item in breaking_changes :
302- print (f"* { item } " )
303- print ()
304-
305- if features :
306- print ("### Features\n " )
307- for item in features :
308- print (f"* { item } " )
309- print ()
310-
311- if bug_fixes :
312- print ("### Bug Fixes\n " )
313- for item in bug_fixes :
314- print (f"* { item } " )
315- print ()
316-
317- if documentation :
318- print ("### Documentation\n " )
319- for item in documentation :
320- print (f"* { item } " )
321- print ()
322-
323- if dependency_upgrades :
324- print ("### Dependencies\n " )
325- for item in dependency_upgrades :
326- print (f"* { item } " )
327- print ()
269+ if not any ([breaking_changes , features , bug_fixes , dependency_upgrades , documentation ]):
270+ print ("* No change" )
271+ else :
272+ if breaking_changes :
273+ print ("### ⚠ BREAKING CHANGES\n " )
274+ for item in breaking_changes :
275+ print (f"* { item } " )
276+ print ()
277+
278+ if features :
279+ print ("### Features\n " )
280+ for item in features :
281+ print (f"* { item } " )
282+ print ()
283+
284+ if bug_fixes :
285+ print ("### Bug Fixes\n " )
286+ for item in bug_fixes :
287+ print (f"* { item } " )
288+ print ()
289+
290+ if documentation :
291+ print ("### Documentation\n " )
292+ for item in documentation :
293+ print (f"* { item } " )
294+ print ()
295+
296+ if dependency_upgrades :
297+ print ("### Dependencies\n " )
298+ for item in dependency_upgrades :
299+ print (f"* { item } " )
300+ print ()
328301
329302
330303
0 commit comments