From b38cdb5e3e28c555cfdc0f425d969ce536bf9ca7 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Thu, 23 Apr 2026 12:15:56 -0400 Subject: [PATCH 01/16] chore: add script to generate release notes based on commit history The script takes the following inputs: - module name: as specified in versions.txt - module directory: path in the monorepo - version: version as found in versions.txt It scans backwards through the git history of versions.txt to find the commit where the version was changed to the provided version. It then finds the commit where the previous non-snapshot version was set. It uses this commit range to generate the commit history affecting that directory. --- .../generate_module_notes.py | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 .github/release-note-generation/generate_module_notes.py diff --git a/.github/release-note-generation/generate_module_notes.py b/.github/release-note-generation/generate_module_notes.py new file mode 100644 index 000000000000..430b75451612 --- /dev/null +++ b/.github/release-note-generation/generate_module_notes.py @@ -0,0 +1,278 @@ +import argparse +import re +import subprocess +import sys + + +def run_cmd(cmd, cwd=None): + """Runs a shell command and returns the output.""" + result = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd + ) + if result.returncode != 0: + print(f"Error running command: {' '.join(cmd)}", file=sys.stderr) + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + return result.stdout + + +def main(): + parser = argparse.ArgumentParser( + description="Generate release notes based on commit history for a specific module." + ) + parser.add_argument( + "--module", required=True, help="Module name as specified in versions.txt" + ) + parser.add_argument( + "--directory", required=True, help="Path in the monorepo where the module has code" + ) + parser.add_argument("--version", required=True, help="Target version") + parser.add_argument( + "--short-name", help="Module short-name used in commit overrides (e.g., aiplatform). Omit for repo-wide generation." + ) + args = parser.parse_args() + + module = args.module + directory = args.directory + target_version = args.version + + # 1. Scan backwards through git history of versions.txt + # We use -G to find commits that modified lines matching the module name. + # We use --first-parent to ignore merge noise. + log_cmd = [ + "git", + "log", + "--oneline", + "--first-parent", + f"-G^{module}:", + "--", + "versions.txt", + ] + log_output = run_cmd(log_cmd) + + commits = [line.split()[0] for line in log_output.splitlines() if line] + + target_commit = None + prev_commit = None + prev_version = None + + for commit in commits: + # Get content of versions.txt at this commit + show_cmd = ["git", "show", f"{commit}:versions.txt"] + try: + content = run_cmd(show_cmd) + except SystemExit: + continue # Ignore errors if file couldn't be read + + # Find the line for the module + pattern = re.compile(rf"^{module}:([^:]+):([^:]+)$") + for line in content.splitlines(): + match = pattern.match(line) + if match: + released_ver = match.group(1) + current_ver = match.group(2) + + # Condition for target version + if released_ver == target_version and not target_commit: + target_commit = commit + print(f"Found target version {target_version} at {commit}") + + # Condition for previous non-snapshot version + # We ignore snapshot versions by checking both fields. + elif ( + target_commit + and released_ver != target_version + and "-SNAPSHOT" not in released_ver + and "-SNAPSHOT" not in current_ver + ): + prev_commit = commit + prev_version = released_ver + print(f"Found previous version {released_ver} at {commit}") + break + if prev_commit: + break + + if not target_commit: + print( + f"Target version {target_version} not found in history for module {module}." + ) + sys.exit(1) + + # Fallback for initial version if no previous version found + if not prev_commit: + print( + f"Previous version not found in history for module {module}." + ) + # Find the first commit affecting that directory + first_commit_cmd = [ + "git", + "log", + "--reverse", + "--oneline", + "--first-parent", + "--", + directory, + ] + try: + first_commit_output = run_cmd(first_commit_cmd) + if first_commit_output: + prev_commit = first_commit_output.splitlines()[0].split()[0] + print(f"Using first commit affecting directory as base: {prev_commit}") + else: + print(f"No history found for directory {directory}.") + sys.exit(1) + except SystemExit: + sys.exit(1) + + print( + f"Generating notes between {prev_commit} and {target_commit} for directory {directory}" + ) + + # 2. Generate commit history in that range affecting that directory + # Use --first-parent to ignore merge noise. + # Use format that includes hash, subject, and body + notes_cmd = [ + "git", + "log", + "--format=%H %s%n%b%n--END_OF_COMMIT--", + "--first-parent", + f"{prev_commit}..{target_commit}", + "--", + directory, + ] + notes_output = run_cmd(notes_cmd) + + # Filter commit titles based on allowed prefixes and categorize them + # Supports scopes in parentheses, e.g., feat(spanner): + prefix_regex = re.compile(r"^(feat|fix|deps|docs)(\([^)]+\))?(!)?:") + + breaking_changes = [] + features = [] + bug_fixes = [] + dependency_upgrades = [] + documentation = [] + + def categorize_and_append(commit_hash, text): + match = prefix_regex.match(text) + if not match: + return + + prefix = match.group(1) + is_breaking = match.group(3) == "!" + + if is_breaking: + breaking_changes.append(f"{commit_hash[:11]} {text}") + elif prefix == "feat": + features.append(f"{commit_hash[:11]} {text}") + elif prefix == "fix": + bug_fixes.append(f"{commit_hash[:11]} {text}") + elif prefix == "deps": + dependency_upgrades.append(f"{commit_hash[:11]} {text}") + elif prefix == "docs": + documentation.append(f"{commit_hash[:11]} {text}") + + commits_data = notes_output.split("--END_OF_COMMIT--") + + for commit_data in commits_data: + commit_data = commit_data.strip() + if not commit_data: + continue + + lines = commit_data.splitlines() + if not lines: + continue + + header_parts = lines[0].split(" ", 1) + commit_hash = header_parts[0] + subject = header_parts[1] if len(header_parts) > 1 else "" + + body = "\n".join(lines[1:]) + + # Check for override in the entire message + if "BEGIN_COMMIT_OVERRIDE" in body or "BEGIN_COMMIT_OVERRIDE" in subject: + match = re.search(r"BEGIN_COMMIT_OVERRIDE(.*?)END_COMMIT_OVERRIDE", commit_data, re.DOTALL) + if match: + override_content = match.group(1) + current_item = [] + in_module_item = False + + for line in override_content.splitlines(): + line_stripped = line.strip() + if not line_stripped: + continue + + # Check if it's a new item using regex + is_new_item = prefix_regex.match(line_stripped) + + if is_new_item: + # If we were in an item, save it + if in_module_item and current_item: + categorize_and_append(commit_hash, " ".join(current_item)) + current_item = [] + in_module_item = False + + # Check if this new item is for our module or if we want all + should_include = False + if args.short_name: + if f"[{args.short_name}]" in line_stripped: + should_include = True + else: + should_include = True + + if should_include: + in_module_item = True + current_item.append(line_stripped) + elif in_module_item: + # Continuation line + if line_stripped.startswith(("PiperOrigin-RevId:", "Source Link:")): + continue + if line_stripped in ("END_NESTED_COMMIT", "BEGIN_NESTED_COMMIT"): + continue + current_item.append(line_stripped) + + # Save the last item if we were in one + if in_module_item and current_item: + categorize_and_append(commit_hash, " ".join(current_item)) + + # Ignore the title since there was an override + continue + + # Fallback to title check if no override + if prefix_regex.match(subject): + categorize_and_append(commit_hash, subject) + + print("\nRelease Notes:") + if breaking_changes: + print("### ⚠ BREAKING CHANGES\n") + for item in breaking_changes: + print(f"* {item}") + print() + + if features: + print("### Features\n") + for item in features: + print(f"* {item}") + print() + + if bug_fixes: + print("### Bug Fixes\n") + for item in bug_fixes: + print(f"* {item}") + print() + + if dependency_upgrades: + print("### Dependencies\n") + for item in dependency_upgrades: + print(f"* {item}") + print() + + if documentation: + print("### Documentation\n") + for item in documentation: + print(f"* {item}") + print() + + + +if __name__ == "__main__": + main() From c21db4d173f9a03f3a7f91d9906526621425e41b Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Thu, 23 Apr 2026 14:43:53 -0400 Subject: [PATCH 02/16] chore: apply regex escaping and stderr logging improvements to release note generator Addresses feedback from gemini-code-assist: - Escaped module name in regex patterns. - Redirected informational logs to stderr. - Adjusted initial release range logic to include the full history. --- .../generate_module_notes.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/release-note-generation/generate_module_notes.py b/.github/release-note-generation/generate_module_notes.py index 430b75451612..1b18b137d05a 100644 --- a/.github/release-note-generation/generate_module_notes.py +++ b/.github/release-note-generation/generate_module_notes.py @@ -44,7 +44,7 @@ def main(): "log", "--oneline", "--first-parent", - f"-G^{module}:", + f"-G^{re.escape(module)}:", "--", "versions.txt", ] @@ -65,7 +65,7 @@ def main(): continue # Ignore errors if file couldn't be read # Find the line for the module - pattern = re.compile(rf"^{module}:([^:]+):([^:]+)$") + pattern = re.compile(rf"^{re.escape(module)}:([^:]+):([^:]+)$") for line in content.splitlines(): match = pattern.match(line) if match: @@ -75,7 +75,7 @@ def main(): # Condition for target version if released_ver == target_version and not target_commit: target_commit = commit - print(f"Found target version {target_version} at {commit}") + print(f"Found target version {target_version} at {commit}", file=sys.stderr) # Condition for previous non-snapshot version # We ignore snapshot versions by checking both fields. @@ -87,7 +87,7 @@ def main(): ): prev_commit = commit prev_version = released_ver - print(f"Found previous version {released_ver} at {commit}") + print(f"Found previous version {released_ver} at {commit}", file=sys.stderr) break if prev_commit: break @@ -101,7 +101,7 @@ def main(): # Fallback for initial version if no previous version found if not prev_commit: print( - f"Previous version not found in history for module {module}." + f"Previous version not found in history for module {module}.", file=sys.stderr ) # Find the first commit affecting that directory first_commit_cmd = [ @@ -116,16 +116,17 @@ def main(): try: first_commit_output = run_cmd(first_commit_cmd) if first_commit_output: - prev_commit = first_commit_output.splitlines()[0].split()[0] - print(f"Using first commit affecting directory as base: {prev_commit}") + prev_commit = None + print(f"No previous version found. Generating notes from the beginning of history for {directory}.", file=sys.stderr) else: - print(f"No history found for directory {directory}.") + print(f"No history found for directory {directory}.", file=sys.stderr) sys.exit(1) except SystemExit: sys.exit(1) + range_desc = f"between {prev_commit} and {target_commit}" if prev_commit else f"up to {target_commit}" print( - f"Generating notes between {prev_commit} and {target_commit} for directory {directory}" + f"Generating notes {range_desc} for directory {directory}", file=sys.stderr ) # 2. Generate commit history in that range affecting that directory @@ -136,7 +137,7 @@ def main(): "log", "--format=%H %s%n%b%n--END_OF_COMMIT--", "--first-parent", - f"{prev_commit}..{target_commit}", + f"{prev_commit}..{target_commit}" if prev_commit else target_commit, "--", directory, ] From dffcd62b645bee5f8938fe1606f03df00aad77bc Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Thu, 23 Apr 2026 14:44:21 -0400 Subject: [PATCH 03/16] chore: add golden file tests for release note generator Adds unit tests that compare the script output against saved golden files: - Root generation for monorepo version 1.85.0. - Module generation for java-run at version 0.71.0. --- .../test_generate_module_notes.py | 62 +++++++++++++++++++ .../testdata/golden_java-run_0.71.0.txt | 12 ++++ .../testdata/golden_root_1.85.0.txt | 45 ++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 .github/release-note-generation/test_generate_module_notes.py create mode 100644 .github/release-note-generation/testdata/golden_java-run_0.71.0.txt create mode 100644 .github/release-note-generation/testdata/golden_root_1.85.0.txt diff --git a/.github/release-note-generation/test_generate_module_notes.py b/.github/release-note-generation/test_generate_module_notes.py new file mode 100644 index 000000000000..487a43a31c85 --- /dev/null +++ b/.github/release-note-generation/test_generate_module_notes.py @@ -0,0 +1,62 @@ +import subprocess +import unittest +from pathlib import Path + + +class TestGenerateModuleNotes(unittest.TestCase): + + def setUp(self): + self.script_path = Path( + ".github/release-note-generation/generate_module_notes.py" + ) + self.testdata_dir = Path(".github/release-note-generation/testdata") + + def test_java_run_generation(self): + golden_file = self.testdata_dir / "golden_java-run_0.71.0.txt" + with open(golden_file, "r") as f: + expected_output = f.read() + + cmd = [ + "python3", + str(self.script_path), + "--module", + "google-cloud-run", + "--directory", + "java-run", + "--version", + "0.71.0", + "--short-name", + "run", + ] + result = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + + self.assertEqual(result.returncode, 0) + self.assertEqual(result.stdout, expected_output) + + def test_root_generation(self): + golden_file = self.testdata_dir / "golden_root_1.85.0.txt" + with open(golden_file, "r") as f: + expected_output = f.read() + + cmd = [ + "python3", + str(self.script_path), + "--module", + "google-cloud-java", + "--directory", + ".", + "--version", + "1.85.0", + ] + result = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + + self.assertEqual(result.returncode, 0) + self.assertEqual(result.stdout, expected_output) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt b/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt new file mode 100644 index 000000000000..29ae72233902 --- /dev/null +++ b/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt @@ -0,0 +1,12 @@ + +Release Notes: +### ⚠ BREAKING CHANGES + +* 9f28cd5bcd9 fix!: [run] An existing resource_definition `cloudbuild.googleapis.com/WorkerPool` is removed +* 9f28cd5bcd9 fix!: [run] A type of an existing resource_reference option of the field `worker_pool` in message `.google.cloud.run.v2.SubmitBuildRequest` is changed from `cloudbuild.googleapis.com/WorkerPool` to `cloudbuild.googleapis.com/BuildWorkerPool` +* 9f28cd5bcd9 fix!: [run] A type of an existing resource_reference option of the field `worker_pool` in message `.google.cloud.run.v2.BuildConfig` is changed from `cloudbuild.googleapis.com/WorkerPool` to `cloudbuild.googleapis.com/BuildWorkerPool` + +### Features + +* 9f28cd5bcd9 feat: [run] Adding new resource tpye run.googleapis.com/WorkerPool. [googleapis/googleapis@0998e04](https://github.com/googleapis/googleapis/commit/0998e045cf83a1307ceb158e3da304bdaff5bb3a) + diff --git a/.github/release-note-generation/testdata/golden_root_1.85.0.txt b/.github/release-note-generation/testdata/golden_root_1.85.0.txt new file mode 100644 index 000000000000..c133c5cd3cbd --- /dev/null +++ b/.github/release-note-generation/testdata/golden_root_1.85.0.txt @@ -0,0 +1,45 @@ + +Release Notes: +### Features + +* fc62b1e80fa feat: [chronicle] Add DataTableService to Chronicle v1 Client Libraries [googleapis/googleapis@e182cf5](https://github.com/googleapis/googleapis/commit/e182cf5152967047b763fd88f03094cfc836d194) +* fc62b1e80fa feat: [vectorsearch] Added CMEK support +* fc62b1e80fa feat: [vectorsearch] Added UpdateIndex support +* fc62b1e80fa feat: [discoveryengine] add AUTO condition to SearchAsYouTypeSpec in v1alpha and v1beta [googleapis/googleapis@f01ba6b](https://github.com/googleapis/googleapis/commit/f01ba6bda9ef3a45069a699767ee7dc46f30028a) +* fc62b1e80fa feat: [kms] support external-μ in the Digest [googleapis/googleapis@7fbf256](https://github.com/googleapis/googleapis/commit/7fbf256c9ee4e580bc2ffa825d8d41263d9462d3) +* fc62b1e80fa feat: [kms] add a variable to SingleTenantHsmInstanceCreate to control whether future key portability features will be usable on the instance [googleapis/googleapis@bc600b8](https://github.com/googleapis/googleapis/commit/bc600b8b72913d10eaf1793a0845643fda94e4eb) +* fc62b1e80fa feat: [databasecenter] Add support for BigQuery datasets and reservations +* fc62b1e80fa feat: [databasecenter] Introduce resource affiliation and lineage tracking +* fc62b1e80fa feat: [databasecenter] Enhance maintenance information with state, upcoming maintenance, and failure reasons [googleapis/googleapis@7f9e9ff](https://github.com/googleapis/googleapis/commit/7f9e9ff15720fac72c4ba3212343ba6e6102c920) +* fc62b1e80fa feat: [shopping-merchant-inventories] a new field `base64_encoded_name` is added to the `LocalInventory` message +* fc62b1e80fa feat: [shopping-merchant-inventories] new field `base64_encoded_name` is added to the `RegionalInventory` message [googleapis/googleapis@6db5d2e](https://github.com/googleapis/googleapis/commit/6db5d2e6bc3a762fccebbcbcfb8681a6ebaf008e) +* fc62b1e80fa feat: [dataplex] Allow Data Documentation DataScans to support BigQuery Dataset resources in addition to BigQuery table resources +* fc62b1e80fa feat: [dataproc] Add `Engine` field to support LightningEngine in clusters and add support for stop ttl [googleapis/googleapis@2da8658](https://github.com/googleapis/googleapis/commit/2da86587126416eb48d561cd800bb03afa2f501a) +* fc62b1e80fa feat: [shopping-merchant-products] a new field `base64_encoded_name` is added to the `Product` message +* fc62b1e80fa feat: [shopping-merchant-products] new fields - `base64_encoded_name` and `base64_encoded_product` added to the `ProductInput` message +* fc62b1e80fa feat: [infra-manager] adding DeploymentGroups, you can now manage deployment of multiple module root dependencies in a single DAG [googleapis/googleapis@f5cb7af](https://github.com/googleapis/googleapis/commit/f5cb7afc40b63d52f43bc306cb9b64a87b681aea) +* 050187d934f feat: [appoptimize] new module for appoptimize (#12768) + +### Bug Fixes + +* 4bed8fd118a fix(datastore): Create a plaintext gRPC transport channel when using the Emulator (#12721) +* 8e6ba3662d3 fix: correct build directory paths in graalvm cloudbuild.yaml (#12794) +* ac69c8d9041 fix(bqjdbc): Revert DatabaseMetaData field to be non-static in BigQueryConnection (#12778) +* 80dfac6773b fix: update appoptimize version to 0.0.1 to match released repo (#12782) +* fc62b1e80fa fix(deps): update the Java code generator (gapic-generator-java) to 2.69.0 +* 72e5508669e fix(bqjdbc): lazily instantiate Statement in BigQueryDatabaseMetaData (#12752) +* bf926fb23a0 fix(gdch): support EC private keys (#1896) +* 30088d21401 fix(auth): Address ClientSideCredentialAccessBoundary RefreshTask race condition (#12681) + +### Documentation + +* fc62b1e80fa docs: [vectorsearch] Updated documentation for listing locations +* fc62b1e80fa docs: [vectorsearch] Updated documentation for Collection.data_schema [googleapis/googleapis@8d0f6d8](https://github.com/googleapis/googleapis/commit/8d0f6d8615c72d1907aeec8984d68df50fb6b697) +* fc62b1e80fa docs: [shopping-merchant-inventories] A comment for field `name` in message `.google.shopping.merchant.products.v1.LocalInventory` is changed +* fc62b1e80fa docs: [shopping-merchant-inventories] A comment for field `name` in message `.google.shopping.merchant.products.v1.RegionalInventory` is changed +* fc62b1e80fa docs: [dataplex] A comment for message `DataDocumentationResult` is changed +* fc62b1e80fa docs: [dataplex] A comment for field `table_result` in message `.google.cloud.dataplex.v1.DataDocumentationResult` is changed [googleapis/googleapis@1991351](https://github.com/googleapis/googleapis/commit/19913519dae24b82f58b8f1b43822c2c020a123c) +* fc62b1e80fa docs: [network-management] Update comment for the `region` field in `RouteInfo` [googleapis/googleapis@66fcc02](https://github.com/googleapis/googleapis/commit/66fcc021fec9e5249e69da17068bea999f113622) chore: [dialogflow-cx] Add ruby_package to missing proto files in google-cloud-dialogflow-cx-v3 [googleapis/googleapis@b6669d7](https://github.com/googleapis/googleapis/commit/b6669d761c84c04682270ae5610106eb81ce1706) +* fc62b1e80fa docs: [shopping-merchant-products] A comment for field `name` in message `.google.shopping.merchant.products.v1.ProductInput` is changed +* fc62b1e80fa docs: [shopping-merchant-products] A comment for field `name` in message `.google.shopping.merchant.products.v1.Product` is changed [googleapis/googleapis@2aba484](https://github.com/googleapis/googleapis/commit/2aba48492ae471bfb717f5e9c5a190b7cc1c6636) + From 9580a3d10c548d4c033abcc9b0ffdaf3b26590af Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Thu, 23 Apr 2026 15:22:44 -0400 Subject: [PATCH 04/16] chore: remove --first-parent flag globally in release note generator Removes the --first-parent restriction from all Git commands in the script: - versions.txt history scanning. - Fallback for initial releases. - Commit extraction for release notes. This allows capturing commits from side branches that were not squashed, while relying on prefix filters to remove noise. --- .github/release-note-generation/generate_module_notes.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/release-note-generation/generate_module_notes.py b/.github/release-note-generation/generate_module_notes.py index 1b18b137d05a..d4e342f7d703 100644 --- a/.github/release-note-generation/generate_module_notes.py +++ b/.github/release-note-generation/generate_module_notes.py @@ -38,12 +38,10 @@ def main(): # 1. Scan backwards through git history of versions.txt # We use -G to find commits that modified lines matching the module name. - # We use --first-parent to ignore merge noise. log_cmd = [ "git", "log", "--oneline", - "--first-parent", f"-G^{re.escape(module)}:", "--", "versions.txt", @@ -109,7 +107,6 @@ def main(): "log", "--reverse", "--oneline", - "--first-parent", "--", directory, ] @@ -130,13 +127,11 @@ def main(): ) # 2. Generate commit history in that range affecting that directory - # Use --first-parent to ignore merge noise. # Use format that includes hash, subject, and body notes_cmd = [ "git", "log", "--format=%H %s%n%b%n--END_OF_COMMIT--", - "--first-parent", f"{prev_commit}..{target_commit}" if prev_commit else target_commit, "--", directory, From 678c0180b3e7e43e995b6c59502f21b98319cf03 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Thu, 23 Apr 2026 22:24:43 -0400 Subject: [PATCH 05/16] chore: add advanced pom.xml history fallback to release note generator Implements a fallback to scan pom.xml history when versions.txt lacks records: - Finds the commit that changed the version away from the target version. - Finds the commit that set the previous stable version. - Calculates the exclusive range between these events to avoid boundary overlaps. --- .../generate_module_notes.py | 69 +++++++++++++++---- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/.github/release-note-generation/generate_module_notes.py b/.github/release-note-generation/generate_module_notes.py index d4e342f7d703..e8af273e46c1 100644 --- a/.github/release-note-generation/generate_module_notes.py +++ b/.github/release-note-generation/generate_module_notes.py @@ -99,27 +99,72 @@ def main(): # Fallback for initial version if no previous version found if not prev_commit: print( - f"Previous version not found in history for module {module}.", file=sys.stderr + f"Previous version not found in history of versions.txt for module {module}. Trying pom.xml history...", file=sys.stderr ) - # Find the first commit affecting that directory - first_commit_cmd = [ + + pom_path = f"{directory}/pom.xml" + pom_log_cmd = [ "git", "log", - "--reverse", "--oneline", "--", - directory, + pom_path, ] try: - first_commit_output = run_cmd(first_commit_cmd) - if first_commit_output: - prev_commit = None - print(f"No previous version found. Generating notes from the beginning of history for {directory}.", file=sys.stderr) + pom_log_output = run_cmd(pom_log_cmd) + pom_commits = [line.split()[0] for line in pom_log_output.splitlines() if line] + + prev_commit_in_loop = None + target_end_commit = None + prev_start_commit = None + + for commit in pom_commits: + show_cmd = ["git", "show", f"{commit}:{pom_path}"] + try: + content = run_cmd(show_cmd) + except SystemExit: + continue + + match = re.search(r"([^<]+)", content) + if match: + ver = match.group(1) + + if ver == target_version and not target_end_commit: + # Moving backwards, this is the first commit with target version! + # The previous commit in loop was the one that changed it AWAY from target version! + target_end_commit = prev_commit_in_loop + print( + f"Found commit changing away from {target_version} at {target_end_commit}", + file=sys.stderr, + ) + + elif ( + target_end_commit + and ver != target_version + and "-SNAPSHOT" not in ver + ): + # This is the commit where the previous stable version was set! + prev_start_commit = commit + print( + f"Found previous stable version {ver} at {commit}", + file=sys.stderr, + ) + break + + prev_commit_in_loop = commit + + if prev_start_commit and target_end_commit: + prev_commit = prev_start_commit + # Use W~1 to be exclusive of W (the commit that changed it away) + target_commit = f"{target_end_commit}~1" + print(f"Using range derived from pom.xml: {prev_commit}..{target_commit}", file=sys.stderr) else: - print(f"No history found for directory {directory}.", file=sys.stderr) - sys.exit(1) + print(f"Could not find complete range in pom.xml. Falling back to initial release logic.", file=sys.stderr) + prev_commit = None + except SystemExit: - sys.exit(1) + print(f"Failed to read pom.xml history.", file=sys.stderr) + prev_commit = None range_desc = f"between {prev_commit} and {target_commit}" if prev_commit else f"up to {target_commit}" print( From 4e531933076b94a51623eb3852ad5022dcceec07 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 15:05:21 -0400 Subject: [PATCH 06/16] chore: update release note formatting and golden files Updates the generator script to: - Use commit hashes in compare links. - Add commit links to items. - Retain conventional commit prefixes. - Omit PR links as requested. Also updates the golden files to match the new formatting. --- .../generate_module_notes.py | 41 ++++++---- .../testdata/golden_java-run_0.71.0.txt | 10 +-- .../testdata/golden_root_1.85.0.txt | 75 ++++++++++--------- 3 files changed, 72 insertions(+), 54 deletions(-) diff --git a/.github/release-note-generation/generate_module_notes.py b/.github/release-note-generation/generate_module_notes.py index e8af273e46c1..5dffd1ae9535 100644 --- a/.github/release-note-generation/generate_module_notes.py +++ b/.github/release-note-generation/generate_module_notes.py @@ -185,7 +185,7 @@ def main(): # Filter commit titles based on allowed prefixes and categorize them # Supports scopes in parentheses, e.g., feat(spanner): - prefix_regex = re.compile(r"^(feat|fix|deps|docs)(\([^)]+\))?(!)?:") + prefix_regex = re.compile(r"^(feat|fix|deps|docs|chore\(deps\)|build\(deps\))(\([^)]+\))?(!)?:") breaking_changes = [] features = [] @@ -201,16 +201,19 @@ def categorize_and_append(commit_hash, text): prefix = match.group(1) is_breaking = match.group(3) == "!" + commit_link = f"([{commit_hash[:7]}](https://github.com/googleapis/google-cloud-java/commit/{commit_hash}))" + full_item = f"{text} {commit_link}" + if is_breaking: - breaking_changes.append(f"{commit_hash[:11]} {text}") + breaking_changes.append(full_item) elif prefix == "feat": - features.append(f"{commit_hash[:11]} {text}") + features.append(full_item) elif prefix == "fix": - bug_fixes.append(f"{commit_hash[:11]} {text}") - elif prefix == "deps": - dependency_upgrades.append(f"{commit_hash[:11]} {text}") + bug_fixes.append(full_item) + elif prefix == "deps" or prefix in ("chore(deps)", "build(deps)"): + dependency_upgrades.append(full_item) elif prefix == "docs": - documentation.append(f"{commit_hash[:11]} {text}") + documentation.append(full_item) commits_data = notes_output.split("--END_OF_COMMIT--") @@ -282,7 +285,17 @@ def categorize_and_append(commit_hash, text): if prefix_regex.match(subject): categorize_and_append(commit_hash, subject) - print("\nRelease Notes:") + # Get dates and build header + # We use ~1 to be exclusive of the boundary as requested earlier, but let's be careful. + # If prev_commit is None, we don't set a range. + target_date = run_cmd(["git", "log", "-1", "--format=%cI", target_commit]).strip() + date_str = target_date.split("T")[0] # Get YYYY-MM-DD + + 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}" + + print(f"## [{target_version}]({compare_url}) ({date_str})") + print() + if breaking_changes: print("### ⚠ BREAKING CHANGES\n") for item in breaking_changes: @@ -301,18 +314,18 @@ def categorize_and_append(commit_hash, text): print(f"* {item}") print() - if dependency_upgrades: - print("### Dependencies\n") - for item in dependency_upgrades: - print(f"* {item}") - print() - if documentation: print("### Documentation\n") for item in documentation: print(f"* {item}") print() + if dependency_upgrades: + print("### Dependencies\n") + for item in dependency_upgrades: + print(f"* {item}") + print() + if __name__ == "__main__": diff --git a/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt b/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt index 29ae72233902..49a43be4b6de 100644 --- a/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt +++ b/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt @@ -1,12 +1,12 @@ +## [0.71.0](https://github.com/googleapis/google-cloud-java/compare/12e01b2413f...6674effbd7b) (2025-08-11) -Release Notes: ### ⚠ BREAKING CHANGES -* 9f28cd5bcd9 fix!: [run] An existing resource_definition `cloudbuild.googleapis.com/WorkerPool` is removed -* 9f28cd5bcd9 fix!: [run] A type of an existing resource_reference option of the field `worker_pool` in message `.google.cloud.run.v2.SubmitBuildRequest` is changed from `cloudbuild.googleapis.com/WorkerPool` to `cloudbuild.googleapis.com/BuildWorkerPool` -* 9f28cd5bcd9 fix!: [run] A type of an existing resource_reference option of the field `worker_pool` in message `.google.cloud.run.v2.BuildConfig` is changed from `cloudbuild.googleapis.com/WorkerPool` to `cloudbuild.googleapis.com/BuildWorkerPool` +* fix!: [run] An existing resource_definition `cloudbuild.googleapis.com/WorkerPool` is removed ([9f28cd5](https://github.com/googleapis/google-cloud-java/commit/9f28cd5bcd951333fb2a3847edba015840b5029b)) +* fix!: [run] A type of an existing resource_reference option of the field `worker_pool` in message `.google.cloud.run.v2.SubmitBuildRequest` is changed from `cloudbuild.googleapis.com/WorkerPool` to `cloudbuild.googleapis.com/BuildWorkerPool` ([9f28cd5](https://github.com/googleapis/google-cloud-java/commit/9f28cd5bcd951333fb2a3847edba015840b5029b)) +* fix!: [run] A type of an existing resource_reference option of the field `worker_pool` in message `.google.cloud.run.v2.BuildConfig` is changed from `cloudbuild.googleapis.com/WorkerPool` to `cloudbuild.googleapis.com/BuildWorkerPool` ([9f28cd5](https://github.com/googleapis/google-cloud-java/commit/9f28cd5bcd951333fb2a3847edba015840b5029b)) ### Features -* 9f28cd5bcd9 feat: [run] Adding new resource tpye run.googleapis.com/WorkerPool. [googleapis/googleapis@0998e04](https://github.com/googleapis/googleapis/commit/0998e045cf83a1307ceb158e3da304bdaff5bb3a) +* feat: [run] Adding new resource tpye run.googleapis.com/WorkerPool. [googleapis/googleapis@0998e04](https://github.com/googleapis/googleapis/commit/0998e045cf83a1307ceb158e3da304bdaff5bb3a) ([9f28cd5](https://github.com/googleapis/google-cloud-java/commit/9f28cd5bcd951333fb2a3847edba015840b5029b)) diff --git a/.github/release-note-generation/testdata/golden_root_1.85.0.txt b/.github/release-note-generation/testdata/golden_root_1.85.0.txt index c133c5cd3cbd..afe3f53288d8 100644 --- a/.github/release-note-generation/testdata/golden_root_1.85.0.txt +++ b/.github/release-note-generation/testdata/golden_root_1.85.0.txt @@ -1,45 +1,50 @@ +## [1.85.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...1b0a9a014fd) (2026-04-14) -Release Notes: ### Features -* fc62b1e80fa feat: [chronicle] Add DataTableService to Chronicle v1 Client Libraries [googleapis/googleapis@e182cf5](https://github.com/googleapis/googleapis/commit/e182cf5152967047b763fd88f03094cfc836d194) -* fc62b1e80fa feat: [vectorsearch] Added CMEK support -* fc62b1e80fa feat: [vectorsearch] Added UpdateIndex support -* fc62b1e80fa feat: [discoveryengine] add AUTO condition to SearchAsYouTypeSpec in v1alpha and v1beta [googleapis/googleapis@f01ba6b](https://github.com/googleapis/googleapis/commit/f01ba6bda9ef3a45069a699767ee7dc46f30028a) -* fc62b1e80fa feat: [kms] support external-μ in the Digest [googleapis/googleapis@7fbf256](https://github.com/googleapis/googleapis/commit/7fbf256c9ee4e580bc2ffa825d8d41263d9462d3) -* fc62b1e80fa feat: [kms] add a variable to SingleTenantHsmInstanceCreate to control whether future key portability features will be usable on the instance [googleapis/googleapis@bc600b8](https://github.com/googleapis/googleapis/commit/bc600b8b72913d10eaf1793a0845643fda94e4eb) -* fc62b1e80fa feat: [databasecenter] Add support for BigQuery datasets and reservations -* fc62b1e80fa feat: [databasecenter] Introduce resource affiliation and lineage tracking -* fc62b1e80fa feat: [databasecenter] Enhance maintenance information with state, upcoming maintenance, and failure reasons [googleapis/googleapis@7f9e9ff](https://github.com/googleapis/googleapis/commit/7f9e9ff15720fac72c4ba3212343ba6e6102c920) -* fc62b1e80fa feat: [shopping-merchant-inventories] a new field `base64_encoded_name` is added to the `LocalInventory` message -* fc62b1e80fa feat: [shopping-merchant-inventories] new field `base64_encoded_name` is added to the `RegionalInventory` message [googleapis/googleapis@6db5d2e](https://github.com/googleapis/googleapis/commit/6db5d2e6bc3a762fccebbcbcfb8681a6ebaf008e) -* fc62b1e80fa feat: [dataplex] Allow Data Documentation DataScans to support BigQuery Dataset resources in addition to BigQuery table resources -* fc62b1e80fa feat: [dataproc] Add `Engine` field to support LightningEngine in clusters and add support for stop ttl [googleapis/googleapis@2da8658](https://github.com/googleapis/googleapis/commit/2da86587126416eb48d561cd800bb03afa2f501a) -* fc62b1e80fa feat: [shopping-merchant-products] a new field `base64_encoded_name` is added to the `Product` message -* fc62b1e80fa feat: [shopping-merchant-products] new fields - `base64_encoded_name` and `base64_encoded_product` added to the `ProductInput` message -* fc62b1e80fa feat: [infra-manager] adding DeploymentGroups, you can now manage deployment of multiple module root dependencies in a single DAG [googleapis/googleapis@f5cb7af](https://github.com/googleapis/googleapis/commit/f5cb7afc40b63d52f43bc306cb9b64a87b681aea) -* 050187d934f feat: [appoptimize] new module for appoptimize (#12768) +* feat: [chronicle] Add DataTableService to Chronicle v1 Client Libraries [googleapis/googleapis@e182cf5](https://github.com/googleapis/googleapis/commit/e182cf5152967047b763fd88f03094cfc836d194) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [vectorsearch] Added CMEK support ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [vectorsearch] Added UpdateIndex support ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [discoveryengine] add AUTO condition to SearchAsYouTypeSpec in v1alpha and v1beta [googleapis/googleapis@f01ba6b](https://github.com/googleapis/googleapis/commit/f01ba6bda9ef3a45069a699767ee7dc46f30028a) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [kms] support external-μ in the Digest [googleapis/googleapis@7fbf256](https://github.com/googleapis/googleapis/commit/7fbf256c9ee4e580bc2ffa825d8d41263d9462d3) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [kms] add a variable to SingleTenantHsmInstanceCreate to control whether future key portability features will be usable on the instance [googleapis/googleapis@bc600b8](https://github.com/googleapis/googleapis/commit/bc600b8b72913d10eaf1793a0845643fda94e4eb) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [databasecenter] Add support for BigQuery datasets and reservations ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [databasecenter] Introduce resource affiliation and lineage tracking ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [databasecenter] Enhance maintenance information with state, upcoming maintenance, and failure reasons [googleapis/googleapis@7f9e9ff](https://github.com/googleapis/googleapis/commit/7f9e9ff15720fac72c4ba3212343ba6e6102c920) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [shopping-merchant-inventories] a new field `base64_encoded_name` is added to the `LocalInventory` message ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [shopping-merchant-inventories] new field `base64_encoded_name` is added to the `RegionalInventory` message [googleapis/googleapis@6db5d2e](https://github.com/googleapis/googleapis/commit/6db5d2e6bc3a762fccebbcbcfb8681a6ebaf008e) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [dataplex] Allow Data Documentation DataScans to support BigQuery Dataset resources in addition to BigQuery table resources ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [dataproc] Add `Engine` field to support LightningEngine in clusters and add support for stop ttl [googleapis/googleapis@2da8658](https://github.com/googleapis/googleapis/commit/2da86587126416eb48d561cd800bb03afa2f501a) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [shopping-merchant-products] a new field `base64_encoded_name` is added to the `Product` message ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [shopping-merchant-products] new fields - `base64_encoded_name` and `base64_encoded_product` added to the `ProductInput` message ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [infra-manager] adding DeploymentGroups, you can now manage deployment of multiple module root dependencies in a single DAG [googleapis/googleapis@f5cb7af](https://github.com/googleapis/googleapis/commit/f5cb7afc40b63d52f43bc306cb9b64a87b681aea) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* feat: [appoptimize] new module for appoptimize (#12768) ([050187d](https://github.com/googleapis/google-cloud-java/commit/050187d934fc78139ec2790c04dd4c1e256591d4)) ### Bug Fixes -* 4bed8fd118a fix(datastore): Create a plaintext gRPC transport channel when using the Emulator (#12721) -* 8e6ba3662d3 fix: correct build directory paths in graalvm cloudbuild.yaml (#12794) -* ac69c8d9041 fix(bqjdbc): Revert DatabaseMetaData field to be non-static in BigQueryConnection (#12778) -* 80dfac6773b fix: update appoptimize version to 0.0.1 to match released repo (#12782) -* fc62b1e80fa fix(deps): update the Java code generator (gapic-generator-java) to 2.69.0 -* 72e5508669e fix(bqjdbc): lazily instantiate Statement in BigQueryDatabaseMetaData (#12752) -* bf926fb23a0 fix(gdch): support EC private keys (#1896) -* 30088d21401 fix(auth): Address ClientSideCredentialAccessBoundary RefreshTask race condition (#12681) +* fix(datastore): Create a plaintext gRPC transport channel when using the Emulator (#12721) ([4bed8fd](https://github.com/googleapis/google-cloud-java/commit/4bed8fd118a723c1d82ae3f29e0550d461db695a)) +* fix: correct build directory paths in graalvm cloudbuild.yaml (#12794) ([8e6ba36](https://github.com/googleapis/google-cloud-java/commit/8e6ba3662d31693e21879f31ed52d9c301a026fd)) +* fix(bqjdbc): Revert DatabaseMetaData field to be non-static in BigQueryConnection (#12778) ([ac69c8d](https://github.com/googleapis/google-cloud-java/commit/ac69c8d9041121b7bdd1f7cb7b6b0d2f182ba138)) +* fix: update appoptimize version to 0.0.1 to match released repo (#12782) ([80dfac6](https://github.com/googleapis/google-cloud-java/commit/80dfac6773bfe7e41e1d3f659fa5c9953a4fd83b)) +* fix(deps): update the Java code generator (gapic-generator-java) to 2.69.0 ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* fix(bqjdbc): lazily instantiate Statement in BigQueryDatabaseMetaData (#12752) ([72e5508](https://github.com/googleapis/google-cloud-java/commit/72e5508669ea48cde28f02adfeedfb05cd73fc57)) +* fix(gdch): support EC private keys (#1896) ([bf926fb](https://github.com/googleapis/google-cloud-java/commit/bf926fb23a0ee32b5563af7671af3776ca670126)) +* fix(auth): Address ClientSideCredentialAccessBoundary RefreshTask race condition (#12681) ([30088d2](https://github.com/googleapis/google-cloud-java/commit/30088d2140184b64e841b9864a2b9518f797a686)) ### Documentation -* fc62b1e80fa docs: [vectorsearch] Updated documentation for listing locations -* fc62b1e80fa docs: [vectorsearch] Updated documentation for Collection.data_schema [googleapis/googleapis@8d0f6d8](https://github.com/googleapis/googleapis/commit/8d0f6d8615c72d1907aeec8984d68df50fb6b697) -* fc62b1e80fa docs: [shopping-merchant-inventories] A comment for field `name` in message `.google.shopping.merchant.products.v1.LocalInventory` is changed -* fc62b1e80fa docs: [shopping-merchant-inventories] A comment for field `name` in message `.google.shopping.merchant.products.v1.RegionalInventory` is changed -* fc62b1e80fa docs: [dataplex] A comment for message `DataDocumentationResult` is changed -* fc62b1e80fa docs: [dataplex] A comment for field `table_result` in message `.google.cloud.dataplex.v1.DataDocumentationResult` is changed [googleapis/googleapis@1991351](https://github.com/googleapis/googleapis/commit/19913519dae24b82f58b8f1b43822c2c020a123c) -* fc62b1e80fa docs: [network-management] Update comment for the `region` field in `RouteInfo` [googleapis/googleapis@66fcc02](https://github.com/googleapis/googleapis/commit/66fcc021fec9e5249e69da17068bea999f113622) chore: [dialogflow-cx] Add ruby_package to missing proto files in google-cloud-dialogflow-cx-v3 [googleapis/googleapis@b6669d7](https://github.com/googleapis/googleapis/commit/b6669d761c84c04682270ae5610106eb81ce1706) -* fc62b1e80fa docs: [shopping-merchant-products] A comment for field `name` in message `.google.shopping.merchant.products.v1.ProductInput` is changed -* fc62b1e80fa docs: [shopping-merchant-products] A comment for field `name` in message `.google.shopping.merchant.products.v1.Product` is changed [googleapis/googleapis@2aba484](https://github.com/googleapis/googleapis/commit/2aba48492ae471bfb717f5e9c5a190b7cc1c6636) +* docs: [vectorsearch] Updated documentation for listing locations ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* docs: [vectorsearch] Updated documentation for Collection.data_schema [googleapis/googleapis@8d0f6d8](https://github.com/googleapis/googleapis/commit/8d0f6d8615c72d1907aeec8984d68df50fb6b697) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* docs: [shopping-merchant-inventories] A comment for field `name` in message `.google.shopping.merchant.products.v1.LocalInventory` is changed ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* docs: [shopping-merchant-inventories] A comment for field `name` in message `.google.shopping.merchant.products.v1.RegionalInventory` is changed ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* docs: [dataplex] A comment for message `DataDocumentationResult` is changed ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* docs: [dataplex] A comment for field `table_result` in message `.google.cloud.dataplex.v1.DataDocumentationResult` is changed [googleapis/googleapis@1991351](https://github.com/googleapis/googleapis/commit/19913519dae24b82f58b8f1b43822c2c020a123c) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* docs: [network-management] Update comment for the `region` field in `RouteInfo` [googleapis/googleapis@66fcc02](https://github.com/googleapis/googleapis/commit/66fcc021fec9e5249e69da17068bea999f113622) chore: [dialogflow-cx] Add ruby_package to missing proto files in google-cloud-dialogflow-cx-v3 [googleapis/googleapis@b6669d7](https://github.com/googleapis/googleapis/commit/b6669d761c84c04682270ae5610106eb81ce1706) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* docs: [shopping-merchant-products] A comment for field `name` in message `.google.shopping.merchant.products.v1.ProductInput` is changed ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) +* docs: [shopping-merchant-products] A comment for field `name` in message `.google.shopping.merchant.products.v1.Product` is changed [googleapis/googleapis@2aba484](https://github.com/googleapis/googleapis/commit/2aba48492ae471bfb717f5e9c5a190b7cc1c6636) ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) + +### Dependencies + +* chore(deps): update upper bound dependencies file (#12706) ([c3f13a6](https://github.com/googleapis/google-cloud-java/commit/c3f13a653e76875e4b9ad9e27f572e046bdda3e3)) +* build(deps): upgrade grpc-gcp to 1.10.0 (#12772) ([bb60a6e](https://github.com/googleapis/google-cloud-java/commit/bb60a6ea1a3481bd582e5c45503d2e578740e61c)) From 6223f849e8e599aae1052e146619901a9aef4ea0 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 15:12:53 -0400 Subject: [PATCH 07/16] docs(spanner): backfill missing release notes for v6.113.0 to v6.116.1 Backfills release notes for Spanner versions that were released on Maven Central but missing from the monorepo changelog: - v6.113.0 - v6.114.0 - v6.115.0 - v6.116.0 - v6.116.1 Generated using the custom release note generator script with pom.xml fallback. --- java-spanner/CHANGELOG.md | 47 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/java-spanner/CHANGELOG.md b/java-spanner/CHANGELOG.md index 3e7586cd187b..f6b8fa7118a1 100644 --- a/java-spanner/CHANGELOG.md +++ b/java-spanner/CHANGELOG.md @@ -1,5 +1,50 @@ # Changelog +## [6.116.1](https://github.com/googleapis/google-cloud-java/compare/1b0a9a014fd...cd6048171fc) (2026-04-21) + +### Features + +* feat(spanner): add shared endpoint cooldowns for location-aware rerouting (#12845) ([f5f273b](https://github.com/googleapis/google-cloud-java/commit/f5f273ba0bd6b7ca9a8be7d1b5a89211ef5ff9fc)) + + +## [6.116.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...1b0a9a014fd) (2026-04-14) + + +## [6.115.0](https://github.com/googleapis/google-cloud-java/compare/c2147fc9ab7...5c303706d40) (2026-04-13) + +### Bug Fixes + +* fix(java-spanner): use the existing dependency versions (#12746) ([8650bc6](https://github.com/googleapis/google-cloud-java/commit/8650bc6d907a5ad25f191a3d83d993d6d069694e)) +* fix(spanner): preserve all async cache updates (#12740) ([b8bf432](https://github.com/googleapis/google-cloud-java/commit/b8bf432f48f48bb454ba0ea50e40bfabca0ebc24)) +* fix(spanner): fix grpc-gcp affinity cleanup and multiplexed channel usage leaks (#12726) ([55c9857](https://github.com/googleapis/google-cloud-java/commit/55c985776700b1219ece39a519021030eb18d927)) +* fix(spanner): ensure executeQueryAsync is non-blocking (#12715) ([b7e34d2](https://github.com/googleapis/google-cloud-java/commit/b7e34d22191df6bf7fc2aa30bd83234312dd89c6)) +* fix(spanner): honor built-in metrics opt-out for gRPC metrics exporter (#12711) ([57baaea](https://github.com/googleapis/google-cloud-java/commit/57baaeaae5ef1f819e04b253dfcf570499ccc110)) + + +## [6.114.0](https://github.com/googleapis/google-cloud-java/compare/a35f1c267d8...4042feed629) (2026-04-10) + +### Bug Fixes + +* fix(java-spanner): use the existing dependency versions (#12746) ([8650bc6](https://github.com/googleapis/google-cloud-java/commit/8650bc6d907a5ad25f191a3d83d993d6d069694e)) +* fix(spanner): preserve all async cache updates (#12740) ([b8bf432](https://github.com/googleapis/google-cloud-java/commit/b8bf432f48f48bb454ba0ea50e40bfabca0ebc24)) +* fix(spanner): fix grpc-gcp affinity cleanup and multiplexed channel usage leaks (#12726) ([55c9857](https://github.com/googleapis/google-cloud-java/commit/55c985776700b1219ece39a519021030eb18d927)) +* fix(spanner): ensure executeQueryAsync is non-blocking (#12715) ([b7e34d2](https://github.com/googleapis/google-cloud-java/commit/b7e34d22191df6bf7fc2aa30bd83234312dd89c6)) +* fix(spanner): honor built-in metrics opt-out for gRPC metrics exporter (#12711) ([57baaea](https://github.com/googleapis/google-cloud-java/commit/57baaeaae5ef1f819e04b253dfcf570499ccc110)) +* fix: update Version.java and correct spanner version for 1.83.0 release (#12712) ([c2147fc](https://github.com/googleapis/google-cloud-java/commit/c2147fc9ab767b0546e6f71483e3f0af1a99740c)) + + +## [6.113.0](https://github.com/googleapis/google-cloud-java/compare/8e13cf00a16...c2147fc9ab7~1) (2026-04-08) + +### Features + +* feat: Switch Eef metrics to using built in open telemetry (#4385) ([759bb22](https://github.com/googleapis/google-cloud-java/commit/759bb22da811ac9cdb6f8720a3e22e0b17c8f4e9)) + +### Bug Fixes + +* fix(spanner): enforce READY-only location aware routing and add endpoint lifecycle management ([ecb86fd](https://github.com/googleapis/google-cloud-java/commit/ecb86fd8be5fd6e2d470434d5a7b74afb96220d0)) +* fix(spanner): improve grpc-gcp affinity cleanup and location-aware retries ([a157c2f](https://github.com/googleapis/google-cloud-java/commit/a157c2f09e431bc61dd74b375bfb2dcf812d81f4)) + + ## [6.112.0](https://github.com/googleapis/java-spanner/compare/v6.111.1...v6.112.0) (2026-03-17) @@ -3704,4 +3749,4 @@ Apologies for the inconvenience. ### Dependencies -* update dependency org.jacoco:jacoco-maven-plugin to v0.8.5 ([#7023](https://www.github.com/googleapis/java-spanner/issues/7023)) ([d8b6438](https://www.github.com/googleapis/java-spanner/commit/d8b6438aa3b881c1c9baff584a74813664be4df8)) +* update dependency org.jacoco:jacoco-maven-plugin to v0.8.5 ([#7023](https://www.github.com/googleapis/java-spanner/issues/7023)) ([d8b6438](https://www.github.com/googleapis/java-spanner/commit/d8b6438aa3b881c1c9baff584a74813664be4df8)) \ No newline at end of file From bcdd6d3d8e1d85afd9ed4795260c558039cd2555 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 15:53:39 -0400 Subject: [PATCH 08/16] docs(spanner): backfill missing release notes with strict lifecycle rule Backfills release notes for Spanner versions v6.113.0 to v6.116.1 using the refined lifecycle rule: - Excludes commits with the target version in file state. - Includes commits with previous release version or target snapshot. This accurately reflects when work was done leading up to each release. --- java-spanner/CHANGELOG.md | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/java-spanner/CHANGELOG.md b/java-spanner/CHANGELOG.md index f6b8fa7118a1..363ae646f6c0 100644 --- a/java-spanner/CHANGELOG.md +++ b/java-spanner/CHANGELOG.md @@ -9,6 +9,8 @@ ## [6.116.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...1b0a9a014fd) (2026-04-14) +* No change + ## [6.115.0](https://github.com/googleapis/google-cloud-java/compare/c2147fc9ab7...5c303706d40) (2026-04-13) @@ -23,26 +25,20 @@ ## [6.114.0](https://github.com/googleapis/google-cloud-java/compare/a35f1c267d8...4042feed629) (2026-04-10) -### Bug Fixes - -* fix(java-spanner): use the existing dependency versions (#12746) ([8650bc6](https://github.com/googleapis/google-cloud-java/commit/8650bc6d907a5ad25f191a3d83d993d6d069694e)) -* fix(spanner): preserve all async cache updates (#12740) ([b8bf432](https://github.com/googleapis/google-cloud-java/commit/b8bf432f48f48bb454ba0ea50e40bfabca0ebc24)) -* fix(spanner): fix grpc-gcp affinity cleanup and multiplexed channel usage leaks (#12726) ([55c9857](https://github.com/googleapis/google-cloud-java/commit/55c985776700b1219ece39a519021030eb18d927)) -* fix(spanner): ensure executeQueryAsync is non-blocking (#12715) ([b7e34d2](https://github.com/googleapis/google-cloud-java/commit/b7e34d22191df6bf7fc2aa30bd83234312dd89c6)) -* fix(spanner): honor built-in metrics opt-out for gRPC metrics exporter (#12711) ([57baaea](https://github.com/googleapis/google-cloud-java/commit/57baaeaae5ef1f819e04b253dfcf570499ccc110)) -* fix: update Version.java and correct spanner version for 1.83.0 release (#12712) ([c2147fc](https://github.com/googleapis/google-cloud-java/commit/c2147fc9ab767b0546e6f71483e3f0af1a99740c)) +* No change ## [6.113.0](https://github.com/googleapis/google-cloud-java/compare/8e13cf00a16...c2147fc9ab7~1) (2026-04-08) -### Features +* No change + + + + + -* feat: Switch Eef metrics to using built in open telemetry (#4385) ([759bb22](https://github.com/googleapis/google-cloud-java/commit/759bb22da811ac9cdb6f8720a3e22e0b17c8f4e9)) -### Bug Fixes -* fix(spanner): enforce READY-only location aware routing and add endpoint lifecycle management ([ecb86fd](https://github.com/googleapis/google-cloud-java/commit/ecb86fd8be5fd6e2d470434d5a7b74afb96220d0)) -* fix(spanner): improve grpc-gcp affinity cleanup and location-aware retries ([a157c2f](https://github.com/googleapis/google-cloud-java/commit/a157c2f09e431bc61dd74b375bfb2dcf812d81f4)) ## [6.112.0](https://github.com/googleapis/java-spanner/compare/v6.111.1...v6.112.0) (2026-03-17) From 45c2c44e319822c1d2287f481c1cd14cccab08c6 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 18:06:46 -0400 Subject: [PATCH 09/16] fix: suppress noisy logs and update goldens for release note generator - Adds file existence check before running git show to eliminate noisy errors in helper function. - Updates golden files to match the output with the final rules and forward scan logic. --- .../generate_module_notes.py | 287 ++++++++---------- .../testdata/golden_java-run_0.71.0.txt | 2 +- .../testdata/golden_root_1.85.0.txt | 6 +- 3 files changed, 132 insertions(+), 163 deletions(-) diff --git a/.github/release-note-generation/generate_module_notes.py b/.github/release-note-generation/generate_module_notes.py index 5dffd1ae9535..8e7f400c1c27 100644 --- a/.github/release-note-generation/generate_module_notes.py +++ b/.github/release-note-generation/generate_module_notes.py @@ -16,6 +16,56 @@ def run_cmd(cmd, cwd=None): return result.stdout +def find_version_boundaries(file_path, pattern, target_version, module=None): + """Scans history of a file to find release boundaries moving forward.""" + log_cmd = [ + "git", + "log", + "--oneline", + "--all", + "--", + file_path, + ] + try: + log_output = run_cmd(log_cmd) + commits = [line.split()[0] for line in log_output.splitlines() if line] + commits.reverse() # Move forward in time! + + first_prev_commit = None + target_release_commit = None + prev_version = None + + for commit in commits: + show_cmd = ["git", "show", f"{commit}:{file_path}"] + try: + content = run_cmd(show_cmd) + except SystemExit: + continue + + found_ver = None + match = pattern.search(content) + if match: + found_ver = match.group(1) + + if found_ver: + if found_ver == target_version and not target_release_commit: + target_release_commit = commit + break # Stop as soon as we find the target release! + + if found_ver != target_version and "-SNAPSHOT" not in found_ver: + if not prev_version: + prev_version = found_ver + first_prev_commit = commit + elif found_ver != prev_version: + # Found a newer stable version before hitting target! + prev_version = found_ver + first_prev_commit = commit + + return first_prev_commit, target_release_commit, prev_version + except SystemExit: + return None, None, None + + def main(): parser = argparse.ArgumentParser( description="Generate release notes based on commit history for a specific module." @@ -36,136 +86,24 @@ def main(): directory = args.directory target_version = args.version - # 1. Scan backwards through git history of versions.txt - # We use -G to find commits that modified lines matching the module name. - log_cmd = [ - "git", - "log", - "--oneline", - f"-G^{re.escape(module)}:", - "--", - "versions.txt", - ] - log_output = run_cmd(log_cmd) - - commits = [line.split()[0] for line in log_output.splitlines() if line] - + # 1. Scan history of pom.xml + if directory == ".": + pom_path = "gapic-libraries-bom/pom.xml" + else: + pom_path = f"{directory}/pom.xml" + pom_pattern = re.compile(r"([^<]+)") + + prev_commit, target_release_commit, prev_version = find_version_boundaries(pom_path, pom_pattern, target_version) + target_commit = None - prev_commit = None - prev_version = None - - for commit in commits: - # Get content of versions.txt at this commit - show_cmd = ["git", "show", f"{commit}:versions.txt"] - try: - content = run_cmd(show_cmd) - except SystemExit: - continue # Ignore errors if file couldn't be read - - # Find the line for the module - pattern = re.compile(rf"^{re.escape(module)}:([^:]+):([^:]+)$") - for line in content.splitlines(): - match = pattern.match(line) - if match: - released_ver = match.group(1) - current_ver = match.group(2) - - # Condition for target version - if released_ver == target_version and not target_commit: - target_commit = commit - print(f"Found target version {target_version} at {commit}", file=sys.stderr) - - # Condition for previous non-snapshot version - # We ignore snapshot versions by checking both fields. - elif ( - target_commit - and released_ver != target_version - and "-SNAPSHOT" not in released_ver - and "-SNAPSHOT" not in current_ver - ): - prev_commit = commit - prev_version = released_ver - print(f"Found previous version {released_ver} at {commit}", file=sys.stderr) - break - if prev_commit: - break - + if target_release_commit: + target_commit = target_release_commit + print(f"Found target release commit at {target_release_commit}. Using exclusive upper boundary {target_commit}", file=sys.stderr) + if not target_commit: - print( - f"Target version {target_version} not found in history for module {module}." - ) + print(f"Target version {target_version} not found in history of {pom_path}.", file=sys.stderr) sys.exit(1) - # Fallback for initial version if no previous version found - if not prev_commit: - print( - f"Previous version not found in history of versions.txt for module {module}. Trying pom.xml history...", file=sys.stderr - ) - - pom_path = f"{directory}/pom.xml" - pom_log_cmd = [ - "git", - "log", - "--oneline", - "--", - pom_path, - ] - try: - pom_log_output = run_cmd(pom_log_cmd) - pom_commits = [line.split()[0] for line in pom_log_output.splitlines() if line] - - prev_commit_in_loop = None - target_end_commit = None - prev_start_commit = None - - for commit in pom_commits: - show_cmd = ["git", "show", f"{commit}:{pom_path}"] - try: - content = run_cmd(show_cmd) - except SystemExit: - continue - - match = re.search(r"([^<]+)", content) - if match: - ver = match.group(1) - - if ver == target_version and not target_end_commit: - # Moving backwards, this is the first commit with target version! - # The previous commit in loop was the one that changed it AWAY from target version! - target_end_commit = prev_commit_in_loop - print( - f"Found commit changing away from {target_version} at {target_end_commit}", - file=sys.stderr, - ) - - elif ( - target_end_commit - and ver != target_version - and "-SNAPSHOT" not in ver - ): - # This is the commit where the previous stable version was set! - prev_start_commit = commit - print( - f"Found previous stable version {ver} at {commit}", - file=sys.stderr, - ) - break - - prev_commit_in_loop = commit - - if prev_start_commit and target_end_commit: - prev_commit = prev_start_commit - # Use W~1 to be exclusive of W (the commit that changed it away) - target_commit = f"{target_end_commit}~1" - print(f"Using range derived from pom.xml: {prev_commit}..{target_commit}", file=sys.stderr) - else: - print(f"Could not find complete range in pom.xml. Falling back to initial release logic.", file=sys.stderr) - prev_commit = None - - except SystemExit: - print(f"Failed to read pom.xml history.", file=sys.stderr) - prev_commit = None - range_desc = f"between {prev_commit} and {target_commit}" if prev_commit else f"up to {target_commit}" print( f"Generating notes {range_desc} for directory {directory}", file=sys.stderr @@ -178,11 +116,13 @@ def main(): "log", "--format=%H %s%n%b%n--END_OF_COMMIT--", f"{prev_commit}..{target_commit}" if prev_commit else target_commit, - "--", - directory, ] + if directory != ".": + notes_cmd.extend(["--", directory]) notes_output = run_cmd(notes_cmd) + + # Filter commit titles based on allowed prefixes and categorize them # Supports scopes in parentheses, e.g., feat(spanner): prefix_regex = re.compile(r"^(feat|fix|deps|docs|chore\(deps\)|build\(deps\))(\([^)]+\))?(!)?:") @@ -232,6 +172,36 @@ def categorize_and_append(commit_hash, text): body = "\n".join(lines[1:]) + # Verify if commit belongs to this release based on file state + should_include = False + target_snapshot = f"{target_version}-SNAPSHOT" + allowed_versions = (prev_version, target_snapshot) if prev_version else (target_snapshot,) + try: + if directory == ".": + pom_path = "gapic-libraries-bom/pom.xml" + else: + pom_path = f"{directory}/pom.xml" + # Check if file exists at that commit to avoid noisy errors + check_cmd = ["git", "cat-file", "-e", f"{commit_hash}:{pom_path}"] + check_result = subprocess.run(check_cmd, stderr=subprocess.PIPE) + if check_result.returncode == 0: + content = run_cmd(["git", "show", f"{commit_hash}:{pom_path}"]) + if directory == ".": + pattern = re.compile(r"gapic-libraries-bom\s*pom\s*([^<]+)", re.DOTALL) + else: + pattern = re.compile(rf"{re.escape(module)}\s*([^<]+)", re.DOTALL) + + match = pattern.search(content) + + + if match and match.group(1) in allowed_versions: + should_include = True + except SystemExit: + pass + + if not should_include: + continue + # Check for override in the entire message if "BEGIN_COMMIT_OVERRIDE" in body or "BEGIN_COMMIT_OVERRIDE" in subject: 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): print(f"## [{target_version}]({compare_url}) ({date_str})") print() - if breaking_changes: - print("### ⚠ BREAKING CHANGES\n") - for item in breaking_changes: - print(f"* {item}") - print() - - if features: - print("### Features\n") - for item in features: - print(f"* {item}") - print() - - if bug_fixes: - print("### Bug Fixes\n") - for item in bug_fixes: - print(f"* {item}") - print() - - if documentation: - print("### Documentation\n") - for item in documentation: - print(f"* {item}") - print() - - if dependency_upgrades: - print("### Dependencies\n") - for item in dependency_upgrades: - print(f"* {item}") - print() + if not any([breaking_changes, features, bug_fixes, dependency_upgrades, documentation]): + print("* No change") + else: + if breaking_changes: + print("### ⚠ BREAKING CHANGES\n") + for item in breaking_changes: + print(f"* {item}") + print() + + if features: + print("### Features\n") + for item in features: + print(f"* {item}") + print() + + if bug_fixes: + print("### Bug Fixes\n") + for item in bug_fixes: + print(f"* {item}") + print() + + if documentation: + print("### Documentation\n") + for item in documentation: + print(f"* {item}") + print() + + if dependency_upgrades: + print("### Dependencies\n") + for item in dependency_upgrades: + print(f"* {item}") + print() diff --git a/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt b/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt index 49a43be4b6de..54c9184388d2 100644 --- a/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt +++ b/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt @@ -1,4 +1,4 @@ -## [0.71.0](https://github.com/googleapis/google-cloud-java/compare/12e01b2413f...6674effbd7b) (2025-08-11) +## [0.71.0](https://github.com/googleapis/google-cloud-java/compare/12e01b2413f...bb7de963ded) (2025-08-08) ### ⚠ BREAKING CHANGES diff --git a/.github/release-note-generation/testdata/golden_root_1.85.0.txt b/.github/release-note-generation/testdata/golden_root_1.85.0.txt index afe3f53288d8..caccf6c5b2e1 100644 --- a/.github/release-note-generation/testdata/golden_root_1.85.0.txt +++ b/.github/release-note-generation/testdata/golden_root_1.85.0.txt @@ -1,4 +1,4 @@ -## [1.85.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...1b0a9a014fd) (2026-04-14) +## [1.85.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...fabe31c2a9d) (2026-04-13) ### Features @@ -22,9 +22,6 @@ ### Bug Fixes -* fix(datastore): Create a plaintext gRPC transport channel when using the Emulator (#12721) ([4bed8fd](https://github.com/googleapis/google-cloud-java/commit/4bed8fd118a723c1d82ae3f29e0550d461db695a)) -* fix: correct build directory paths in graalvm cloudbuild.yaml (#12794) ([8e6ba36](https://github.com/googleapis/google-cloud-java/commit/8e6ba3662d31693e21879f31ed52d9c301a026fd)) -* fix(bqjdbc): Revert DatabaseMetaData field to be non-static in BigQueryConnection (#12778) ([ac69c8d](https://github.com/googleapis/google-cloud-java/commit/ac69c8d9041121b7bdd1f7cb7b6b0d2f182ba138)) * fix: update appoptimize version to 0.0.1 to match released repo (#12782) ([80dfac6](https://github.com/googleapis/google-cloud-java/commit/80dfac6773bfe7e41e1d3f659fa5c9953a4fd83b)) * fix(deps): update the Java code generator (gapic-generator-java) to 2.69.0 ([fc62b1e](https://github.com/googleapis/google-cloud-java/commit/fc62b1e80fa239ba8eec34ce1853e6c32126d9f4)) * fix(bqjdbc): lazily instantiate Statement in BigQueryDatabaseMetaData (#12752) ([72e5508](https://github.com/googleapis/google-cloud-java/commit/72e5508669ea48cde28f02adfeedfb05cd73fc57)) @@ -45,6 +42,5 @@ ### Dependencies -* chore(deps): update upper bound dependencies file (#12706) ([c3f13a6](https://github.com/googleapis/google-cloud-java/commit/c3f13a653e76875e4b9ad9e27f572e046bdda3e3)) * build(deps): upgrade grpc-gcp to 1.10.0 (#12772) ([bb60a6e](https://github.com/googleapis/google-cloud-java/commit/bb60a6ea1a3481bd582e5c45503d2e578740e61c)) From bc7af1e24b7583c45f0b37bbdd311c552478b4a4 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 18:09:34 -0400 Subject: [PATCH 10/16] docs(spanner): update changelog with backfilled release notes --- java-spanner/CHANGELOG.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/java-spanner/CHANGELOG.md b/java-spanner/CHANGELOG.md index 363ae646f6c0..e65791e52f17 100644 --- a/java-spanner/CHANGELOG.md +++ b/java-spanner/CHANGELOG.md @@ -1,18 +1,17 @@ # Changelog -## [6.116.1](https://github.com/googleapis/google-cloud-java/compare/1b0a9a014fd...cd6048171fc) (2026-04-21) +## [6.116.1](https://github.com/googleapis/google-cloud-java/compare/966e8a4d99b...cd6048171fc) (2026-04-21) ### Features * feat(spanner): add shared endpoint cooldowns for location-aware rerouting (#12845) ([f5f273b](https://github.com/googleapis/google-cloud-java/commit/f5f273ba0bd6b7ca9a8be7d1b5a89211ef5ff9fc)) -## [6.116.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...1b0a9a014fd) (2026-04-14) +## [6.116.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...fabe31c2a9d) (2026-04-13) * No change - -## [6.115.0](https://github.com/googleapis/google-cloud-java/compare/c2147fc9ab7...5c303706d40) (2026-04-13) +## [6.115.0](https://github.com/googleapis/google-cloud-java/compare/c2147fc9ab7...6bef068b4d8) (2026-04-10) ### Bug Fixes @@ -23,24 +22,17 @@ * fix(spanner): honor built-in metrics opt-out for gRPC metrics exporter (#12711) ([57baaea](https://github.com/googleapis/google-cloud-java/commit/57baaeaae5ef1f819e04b253dfcf570499ccc110)) -## [6.114.0](https://github.com/googleapis/google-cloud-java/compare/a35f1c267d8...4042feed629) (2026-04-10) +## [6.114.0](https://github.com/googleapis/google-cloud-java/compare/55c4e0f125c...c2147fc9ab7) (2026-04-08) * No change - -## [6.113.0](https://github.com/googleapis/google-cloud-java/compare/8e13cf00a16...c2147fc9ab7~1) (2026-04-08) +## [6.113.0](https://github.com/googleapis/google-cloud-java/compare/8e13cf00a16...55c4e0f125c) (2026-03-31) * No change - - - - - - ## [6.112.0](https://github.com/googleapis/java-spanner/compare/v6.111.1...v6.112.0) (2026-03-17) From ed56fd7f1e405685d42fdaf6e7f1ad8b5bedd67a Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 18:48:50 -0400 Subject: [PATCH 11/16] docs(bigquery): update changelog with backfilled release notes --- java-bigquery/CHANGELOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/java-bigquery/CHANGELOG.md b/java-bigquery/CHANGELOG.md index 045e090064b4..a56f2c0939b5 100644 --- a/java-bigquery/CHANGELOG.md +++ b/java-bigquery/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [2.65.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...fabe31c2a9d) (2026-04-13) + +### Bug Fixes + +* fix(bqjdbc): lazily instantiate Statement in BigQueryDatabaseMetaData (#12752) ([72e5508](https://github.com/googleapis/google-cloud-java/commit/72e5508669ea48cde28f02adfeedfb05cd73fc57)) + + +## [2.64.0](https://github.com/googleapis/google-cloud-java/compare/0fe7d3822cc...6bef068b4d8) (2026-04-10) + +* No change + ## 2.62.0 (None) * No change @@ -3525,4 +3536,4 @@ ### Documentation -* Update libraries-bom version ([#73](https://www.github.com/googleapis/java-bigquery/issues/73)) ([e967e10](https://www.github.com/googleapis/java-bigquery/commit/e967e10267514dfbac7013cac61f22b74d52b2b8)) +* Update libraries-bom version ([#73](https://www.github.com/googleapis/java-bigquery/issues/73)) ([e967e10](https://www.github.com/googleapis/java-bigquery/commit/e967e10267514dfbac7013cac61f22b74d52b2b8)) \ No newline at end of file From 821bbd0ddf93ff095fe994d8a79a26d99352b06b Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 20:27:52 -0400 Subject: [PATCH 12/16] fix: suppress noisy logs in helper function for release note generator --- .github/release-note-generation/generate_module_notes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/release-note-generation/generate_module_notes.py b/.github/release-note-generation/generate_module_notes.py index 8e7f400c1c27..d3370e1100c4 100644 --- a/.github/release-note-generation/generate_module_notes.py +++ b/.github/release-note-generation/generate_module_notes.py @@ -36,6 +36,12 @@ def find_version_boundaries(file_path, pattern, target_version, module=None): prev_version = None for commit in commits: + # Check if file exists at that commit to avoid noisy errors + check_cmd = ["git", "cat-file", "-e", f"{commit}:{file_path}"] + check_result = subprocess.run(check_cmd, stderr=subprocess.PIPE) + if check_result.returncode != 0: + continue + show_cmd = ["git", "show", f"{commit}:{file_path}"] try: content = run_cmd(show_cmd) From 7eb4135fef4eebd86ae799cb3e35fc4362507ea1 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 21:24:44 -0400 Subject: [PATCH 13/16] docs(bigquery): update changelog with backfilled release notes --- java-bigquery/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java-bigquery/CHANGELOG.md b/java-bigquery/CHANGELOG.md index a56f2c0939b5..b5354465d9f9 100644 --- a/java-bigquery/CHANGELOG.md +++ b/java-bigquery/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [2.65.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...fabe31c2a9d) (2026-04-13) +## [2.65.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...v1.85.0) (2026-04-13) ### Bug Fixes @@ -11,6 +11,7 @@ * No change + ## 2.62.0 (None) * No change From 0ed86c99dddab5801721629fd5aed34e81179c62 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 21:25:28 -0400 Subject: [PATCH 14/16] fix: finalize release note generator and update goldens and Spanner changelog - Implements inclusive lower boundary and other refinements in script. - Updates golden files to match the final output. - Updates Spanner changelog with the final backfilled notes. --- .../generate_module_notes.py | 190 ++++++++++-------- .../testdata/golden_java-run_0.71.0.txt | 2 +- .../testdata/golden_root_1.85.0.txt | 2 +- java-spanner/CHANGELOG.md | 9 +- 4 files changed, 111 insertions(+), 92 deletions(-) diff --git a/.github/release-note-generation/generate_module_notes.py b/.github/release-note-generation/generate_module_notes.py index d3370e1100c4..4e2a6ac487e6 100644 --- a/.github/release-note-generation/generate_module_notes.py +++ b/.github/release-note-generation/generate_module_notes.py @@ -54,24 +54,109 @@ def find_version_boundaries(file_path, pattern, target_version, module=None): found_ver = match.group(1) if found_ver: - if found_ver == target_version and not target_release_commit: + if found_ver == target_version: target_release_commit = commit break # Stop as soon as we find the target release! - if found_ver != target_version and "-SNAPSHOT" not in found_ver: - if not prev_version: - prev_version = found_ver - first_prev_commit = commit - elif found_ver != prev_version: - # Found a newer stable version before hitting target! - prev_version = found_ver - first_prev_commit = commit + # Track the first occurrence of the latest stable version before target + if found_ver != target_version and "-SNAPSHOT" not in found_ver and (not prev_version or found_ver != prev_version): + prev_version = found_ver + first_prev_commit = commit return first_prev_commit, target_release_commit, prev_version except SystemExit: return None, None, None +def verify_commit(commit_hash, directory, module, allowed_versions): + """Verifies if a commit belongs to the release based on file state.""" + if directory == ".": + pom_path = "gapic-libraries-bom/pom.xml" + else: + pom_path = f"{directory}/pom.xml" + + # Check if file exists at that commit to avoid noisy errors + check_cmd = ["git", "cat-file", "-e", f"{commit_hash}:{pom_path}"] + check_result = subprocess.run(check_cmd, stderr=subprocess.PIPE) + if check_result.returncode != 0: + return False + + try: + content = run_cmd(["git", "show", f"{commit_hash}:{pom_path}"]) + # Allow optional tag in between artifactId and version + pattern = re.compile(rf"{re.escape(module)}\s*(?:[^<]+\s*)?([^<]+)", re.DOTALL) + + match = pattern.search(content) + if match and match.group(1) in allowed_versions: + return True + except SystemExit: + pass + + return False + + +def parse_commit_overrides(commit_data, short_name, prefix_regex, commit_hash, categorize_callback): + """Parses commit overrides and calls callback for each item.""" + match = re.search(r"BEGIN_COMMIT_OVERRIDE(.*?)END_COMMIT_OVERRIDE", commit_data, re.DOTALL) + if not match: + return False + + override_content = match.group(1) + current_item = [] + in_module_item = False + + for line in override_content.splitlines(): + line_stripped = line.strip() + if not line_stripped: + continue + + is_new_item = prefix_regex.match(line_stripped) + + if is_new_item: + if in_module_item and current_item: + categorize_callback(commit_hash, " ".join(current_item)) + current_item = [] + in_module_item = False + + should_include = False + if short_name: + if f"[{short_name}]" in line_stripped: + should_include = True + else: + should_include = True + + if should_include: + in_module_item = True + current_item.append(line_stripped) + elif in_module_item: + if line_stripped.startswith(("PiperOrigin-RevId:", "Source Link:")): + continue + if line_stripped in ("END_NESTED_COMMIT", "BEGIN_NESTED_COMMIT"): + continue + current_item.append(line_stripped) + + if in_module_item and current_item: + categorize_callback(commit_hash, " ".join(current_item)) + + return True + + +def get_tag_or_commit(commit_hash): + """Returns the tag pointing at the commit if there is exactly one, else the commit hash.""" + if not commit_hash: + return None + try: + # Remove ~1 if present to find the actual tag pointing at the commit + clean_hash = commit_hash.split("~")[0] + tags_output = run_cmd(["git", "tag", "--points-at", clean_hash]) + tags = [line.strip() for line in tags_output.splitlines() if line.strip()] + if len(tags) == 1: + return tags[0] + except SystemExit: + pass + return commit_hash + + def main(): parser = argparse.ArgumentParser( description="Generate release notes based on commit history for a specific module." @@ -104,7 +189,7 @@ def main(): target_commit = None if target_release_commit: target_commit = target_release_commit - print(f"Found target release commit at {target_release_commit}. Using exclusive upper boundary {target_commit}", file=sys.stderr) + print(f"Found target release commit at {target_release_commit}. Using inclusive upper boundary {target_commit}", file=sys.stderr) if not target_commit: print(f"Target version {target_version} not found in history of {pom_path}.", file=sys.stderr) @@ -121,7 +206,7 @@ def main(): "git", "log", "--format=%H %s%n%b%n--END_OF_COMMIT--", - f"{prev_commit}..{target_commit}" if prev_commit else target_commit, + f"{prev_commit}~1..{target_commit}" if prev_commit else target_commit, ] if directory != ".": notes_cmd.extend(["--", directory]) @@ -179,95 +264,30 @@ def categorize_and_append(commit_hash, text): body = "\n".join(lines[1:]) # Verify if commit belongs to this release based on file state - should_include = False target_snapshot = f"{target_version}-SNAPSHOT" allowed_versions = (prev_version, target_snapshot) if prev_version else (target_snapshot,) - try: - if directory == ".": - pom_path = "gapic-libraries-bom/pom.xml" - else: - pom_path = f"{directory}/pom.xml" - # Check if file exists at that commit to avoid noisy errors - check_cmd = ["git", "cat-file", "-e", f"{commit_hash}:{pom_path}"] - check_result = subprocess.run(check_cmd, stderr=subprocess.PIPE) - if check_result.returncode == 0: - content = run_cmd(["git", "show", f"{commit_hash}:{pom_path}"]) - if directory == ".": - pattern = re.compile(r"gapic-libraries-bom\s*pom\s*([^<]+)", re.DOTALL) - else: - pattern = re.compile(rf"{re.escape(module)}\s*([^<]+)", re.DOTALL) - - match = pattern.search(content) - - - if match and match.group(1) in allowed_versions: - should_include = True - except SystemExit: - pass - - if not should_include: + + target_module = "gapic-libraries-bom" if directory == "." else module + if not verify_commit(commit_hash, directory, target_module, allowed_versions): continue # Check for override in the entire message if "BEGIN_COMMIT_OVERRIDE" in body or "BEGIN_COMMIT_OVERRIDE" in subject: - match = re.search(r"BEGIN_COMMIT_OVERRIDE(.*?)END_COMMIT_OVERRIDE", commit_data, re.DOTALL) - if match: - override_content = match.group(1) - current_item = [] - in_module_item = False - - for line in override_content.splitlines(): - line_stripped = line.strip() - if not line_stripped: - continue - - # Check if it's a new item using regex - is_new_item = prefix_regex.match(line_stripped) - - if is_new_item: - # If we were in an item, save it - if in_module_item and current_item: - categorize_and_append(commit_hash, " ".join(current_item)) - current_item = [] - in_module_item = False - - # Check if this new item is for our module or if we want all - should_include = False - if args.short_name: - if f"[{args.short_name}]" in line_stripped: - should_include = True - else: - should_include = True - - if should_include: - in_module_item = True - current_item.append(line_stripped) - elif in_module_item: - # Continuation line - if line_stripped.startswith(("PiperOrigin-RevId:", "Source Link:")): - continue - if line_stripped in ("END_NESTED_COMMIT", "BEGIN_NESTED_COMMIT"): - continue - current_item.append(line_stripped) - - # Save the last item if we were in one - if in_module_item and current_item: - categorize_and_append(commit_hash, " ".join(current_item)) - - # Ignore the title since there was an override - continue + if parse_commit_overrides(commit_data, args.short_name, prefix_regex, commit_hash, categorize_and_append): + continue # Fallback to title check if no override if prefix_regex.match(subject): categorize_and_append(commit_hash, subject) # Get dates and build header - # We use ~1 to be exclusive of the boundary as requested earlier, but let's be careful. - # If prev_commit is None, we don't set a range. target_date = run_cmd(["git", "log", "-1", "--format=%cI", target_commit]).strip() date_str = target_date.split("T")[0] # Get YYYY-MM-DD - 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}" + prev_ref = get_tag_or_commit(prev_commit) + target_ref = get_tag_or_commit(target_commit) + + 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}" print(f"## [{target_version}]({compare_url}) ({date_str})") print() diff --git a/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt b/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt index 54c9184388d2..6b60c2402348 100644 --- a/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt +++ b/.github/release-note-generation/testdata/golden_java-run_0.71.0.txt @@ -1,4 +1,4 @@ -## [0.71.0](https://github.com/googleapis/google-cloud-java/compare/12e01b2413f...bb7de963ded) (2025-08-08) +## [0.71.0](https://github.com/googleapis/google-cloud-java/compare/v1.64.0...v1.65.0) (2025-08-08) ### ⚠ BREAKING CHANGES diff --git a/.github/release-note-generation/testdata/golden_root_1.85.0.txt b/.github/release-note-generation/testdata/golden_root_1.85.0.txt index caccf6c5b2e1..b149d928ddfa 100644 --- a/.github/release-note-generation/testdata/golden_root_1.85.0.txt +++ b/.github/release-note-generation/testdata/golden_root_1.85.0.txt @@ -1,4 +1,4 @@ -## [1.85.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...fabe31c2a9d) (2026-04-13) +## [1.85.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...v1.85.0) (2026-04-13) ### Features diff --git a/java-spanner/CHANGELOG.md b/java-spanner/CHANGELOG.md index e65791e52f17..63c8bf1e9de3 100644 --- a/java-spanner/CHANGELOG.md +++ b/java-spanner/CHANGELOG.md @@ -7,11 +7,11 @@ * feat(spanner): add shared endpoint cooldowns for location-aware rerouting (#12845) ([f5f273b](https://github.com/googleapis/google-cloud-java/commit/f5f273ba0bd6b7ca9a8be7d1b5a89211ef5ff9fc)) -## [6.116.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...fabe31c2a9d) (2026-04-13) +## [6.116.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...v1.85.0) (2026-04-13) * No change -## [6.115.0](https://github.com/googleapis/google-cloud-java/compare/c2147fc9ab7...6bef068b4d8) (2026-04-10) +## [6.115.0](https://github.com/googleapis/google-cloud-java/compare/v1.83.0-sdk-platform-java...6bef068b4d8) (2026-04-10) ### Bug Fixes @@ -20,9 +20,10 @@ * fix(spanner): fix grpc-gcp affinity cleanup and multiplexed channel usage leaks (#12726) ([55c9857](https://github.com/googleapis/google-cloud-java/commit/55c985776700b1219ece39a519021030eb18d927)) * fix(spanner): ensure executeQueryAsync is non-blocking (#12715) ([b7e34d2](https://github.com/googleapis/google-cloud-java/commit/b7e34d22191df6bf7fc2aa30bd83234312dd89c6)) * fix(spanner): honor built-in metrics opt-out for gRPC metrics exporter (#12711) ([57baaea](https://github.com/googleapis/google-cloud-java/commit/57baaeaae5ef1f819e04b253dfcf570499ccc110)) +* fix: update Version.java and correct spanner version for 1.83.0 release (#12712) ([c2147fc](https://github.com/googleapis/google-cloud-java/commit/c2147fc9ab767b0546e6f71483e3f0af1a99740c)) -## [6.114.0](https://github.com/googleapis/google-cloud-java/compare/55c4e0f125c...c2147fc9ab7) (2026-04-08) +## [6.114.0](https://github.com/googleapis/google-cloud-java/compare/55c4e0f125c...v1.83.0-sdk-platform-java) (2026-04-08) * No change @@ -31,8 +32,6 @@ * No change - - ## [6.112.0](https://github.com/googleapis/java-spanner/compare/v6.111.1...v6.112.0) (2026-03-17) From a656a6c2e49c0dd6e0ac0001262cc89518d53832 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 21:41:27 -0400 Subject: [PATCH 15/16] docs(spanner-jdbc): update changelog with backfilled release notes --- java-spanner-jdbc/CHANGELOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/java-spanner-jdbc/CHANGELOG.md b/java-spanner-jdbc/CHANGELOG.md index eca8e75685bf..3115a0cc6f26 100644 --- a/java-spanner-jdbc/CHANGELOG.md +++ b/java-spanner-jdbc/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [2.38.0](https://github.com/googleapis/google-cloud-java/compare/6bef068b4d8...v1.85.0) (2026-04-13) + +* No change + +## [2.37.0](https://github.com/googleapis/google-cloud-java/compare/55c4e0f125c...6bef068b4d8) (2026-04-10) + +### Bug Fixes + +* fix: update Version.java and correct spanner version for 1.83.0 release (#12712) ([c2147fc](https://github.com/googleapis/google-cloud-java/commit/c2147fc9ab767b0546e6f71483e3f0af1a99740c)) + + ## [2.35.4](https://github.com/googleapis/java-spanner-jdbc/compare/v2.35.3...v2.35.4) (2026-03-04) @@ -2238,4 +2249,4 @@ ### Dependencies * update core dependencies ([#25](https://www.github.com/googleapis/java-spanner-jdbc/issues/25)) ([9f4f4ad](https://www.github.com/googleapis/java-spanner-jdbc/commit/9f4f4ad1b076bd3131296c9f7f6558f2bc885d42)) -* update dependency org.threeten:threetenbp to v1.4.1 ([7cc951b](https://www.github.com/googleapis/java-spanner-jdbc/commit/7cc951b340b072e7853df868aaf7c17f854a69f5)) +* update dependency org.threeten:threetenbp to v1.4.1 ([7cc951b](https://www.github.com/googleapis/java-spanner-jdbc/commit/7cc951b340b072e7853df868aaf7c17f854a69f5)) \ No newline at end of file From 2220fc1543e9fea1c27f3f4e589ceae3387c1d17 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 24 Apr 2026 21:42:50 -0400 Subject: [PATCH 16/16] fix: handle multiple tags in release note generator --- .../release-note-generation/generate_module_notes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/release-note-generation/generate_module_notes.py b/.github/release-note-generation/generate_module_notes.py index 4e2a6ac487e6..99fcd2399ce3 100644 --- a/.github/release-note-generation/generate_module_notes.py +++ b/.github/release-note-generation/generate_module_notes.py @@ -141,7 +141,7 @@ def parse_commit_overrides(commit_data, short_name, prefix_regex, commit_hash, c return True -def get_tag_or_commit(commit_hash): +def get_tag_or_commit(commit_hash, target_version): """Returns the tag pointing at the commit if there is exactly one, else the commit hash.""" if not commit_hash: return None @@ -152,6 +152,10 @@ def get_tag_or_commit(commit_hash): tags = [line.strip() for line in tags_output.splitlines() if line.strip()] if len(tags) == 1: return tags[0] + elif len(tags) > 1: + for tag in tags: + if target_version in tag: + return tag except SystemExit: pass return commit_hash @@ -284,8 +288,8 @@ def categorize_and_append(commit_hash, text): target_date = run_cmd(["git", "log", "-1", "--format=%cI", target_commit]).strip() date_str = target_date.split("T")[0] # Get YYYY-MM-DD - prev_ref = get_tag_or_commit(prev_commit) - target_ref = get_tag_or_commit(target_commit) + prev_ref = get_tag_or_commit(prev_commit, prev_version) if prev_version else prev_commit + target_ref = get_tag_or_commit(target_commit, target_version) 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}"