Skip to content

CLOUDP-295785 - Calculate next version and release notes script #193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,4 @@ logs-debug/

docs/**/log/*
docs/**/test.sh.run.log
/.venv/
46 changes: 46 additions & 0 deletions scripts/release/calculate_next_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import argparse
import pathlib

from git import Repo

from scripts.release.release_notes import calculate_next_version_with_changelog

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--path",
action="store",
default=".",
type=pathlib.Path,
help="Path to the Git repository. Default is the current directory '.'",
)
parser.add_argument(
"--changelog_path",
default="changelog/",
action="store",
type=str,
help="Path to the changelog directory relative to the repository root. Default is 'changelog/'",
)
parser.add_argument(
"--initial_commit_sha",
action="store",
type=str,
help="SHA of the initial commit to start from if no previous version tag is found.",
)
parser.add_argument(
"--initial_version",
default="1.0.0",
action="store",
type=str,
help="Version to use if no previous version tag is found. Default is '1.0.0'",
)
parser.add_argument("--output", "-o", type=pathlib.Path)
args = parser.parse_args()

repo = Repo(args.path)

version, _ = calculate_next_version_with_changelog(
repo, args.changelog_path, args.initial_commit_sha, args.initial_version
)

print(version)
63 changes: 63 additions & 0 deletions scripts/release/changelog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import os
from enum import StrEnum

from git import Commit, Repo

CHANGELOG_PATH = "changelog/"

PRELUDE_ENTRIES = ["prelude"]
BREAKING_CHANGE_ENTRIES = ["breaking_change", "breaking", "major"]
FEATURE_ENTRIES = ["feat", "feature"]
BUGFIX_ENTRIES = ["fix", "bugfix", "hotfix", "patch"]


class ChangeType(StrEnum):
PRELUDE = "prelude"
BREAKING = "breaking"
FEATURE = "feature"
FIX = "fix"
OTHER = "other"


def get_changelog_entries(
previous_version_commit: Commit,
repo: Repo,
changelog_sub_path: str,
) -> list[tuple[ChangeType, str]]:
changelog = []

# Compare previous version commit with current working tree
diff_index = previous_version_commit.diff(other=repo.head.commit, paths=changelog_sub_path)

# No changes since the previous version
if not diff_index:
return changelog

# Traverse added Diff objects only (change type 'A' for added files)
for diff_item in diff_index.iter_change_type("A"):
file_path = diff_item.b_path
file_name = os.path.basename(file_path)
change_type = get_change_type(file_name)

abs_file_path = os.path.join(repo.working_dir, file_path)
with open(abs_file_path, "r") as file:
file_content = file.read()

changelog.append((change_type, file_content))

return changelog


def get_change_type(file_name: str) -> ChangeType:
"""Extract the change type from the file name."""

if any(entry in file_name.lower() for entry in PRELUDE_ENTRIES):
return ChangeType.PRELUDE
if any(entry in file_name.lower() for entry in BREAKING_CHANGE_ENTRIES):
return ChangeType.BREAKING
elif any(entry in file_name.lower() for entry in FEATURE_ENTRIES):
return ChangeType.FEATURE
elif any(entry in file_name.lower() for entry in BUGFIX_ENTRIES):
return ChangeType.FIX
else:
return ChangeType.OTHER
26 changes: 26 additions & 0 deletions scripts/release/changelog_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from changelog import ChangeType, get_change_type


def test_get_change_type():

# Test prelude
assert get_change_type("20250502_prelude_release_notes.md") == ChangeType.PRELUDE

# Test breaking changes
assert get_change_type("20250101_breaking_change_api_update.md") == ChangeType.BREAKING
assert get_change_type("20250508_breaking_remove_deprecated.md") == ChangeType.BREAKING
assert get_change_type("20250509_major_schema_change.md") == ChangeType.BREAKING

# Test features
assert get_change_type("20250509_feature_new_dashboard.md") == ChangeType.FEATURE
assert get_change_type("20250511_feat_add_metrics.md") == ChangeType.FEATURE

# Test fixes
assert get_change_type("20251210_fix_olm_missing_images.md") == ChangeType.FIX
assert get_change_type("20251010_bugfix_memory_leak.md") == ChangeType.FIX
assert get_change_type("20250302_hotfix_security_issue.md") == ChangeType.FIX
assert get_change_type("20250301_patch_typo_correction.md") == ChangeType.FIX

# Test other
assert get_change_type("20250520_docs_update_readme.md") == ChangeType.OTHER
assert get_change_type("20250610_refactor_codebase.md") == ChangeType.OTHER
162 changes: 162 additions & 0 deletions scripts/release/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import os
import shutil
import tempfile

from _pytest.fixtures import fixture
from git import Repo

from scripts.release.changelog import CHANGELOG_PATH


@fixture(scope="session")
def git_repo(change_log_path: str = CHANGELOG_PATH) -> Repo:
"""
Create a temporary git repository for testing.
Visual representation of the repository structure is in test_git_repo.mmd (mermaid/gitgraph https://mermaid.js.org/syntax/gitgraph.html).
Whenever you modify or add new commits please update the git graph as well.
"""

repo_dir = tempfile.mkdtemp()
repo = Repo.init(repo_dir)
changelog_path = os.path.join(repo_dir, change_log_path)
os.mkdir(changelog_path)

## First commit and 1.0.0 tag
repo.git.checkout("-b", "master")
new_file = create_new_file(repo_dir, "new-file.txt", "Initial content\n")
repo.index.add(new_file)
repo.index.commit("initial commit")
changelog_file = add_file(repo_dir, "changelog/20250506_prelude_mck.md")
repo.index.add(changelog_file)
repo.index.commit("release notes prelude MCK")
repo.create_tag("1.0.0", message="Initial release")

## Bug fixes and 1.0.1 tag
file_name = create_new_file(repo_dir, "another-file.txt", "Added more content\n")
changelog_file = add_file(repo_dir, "changelog/20250510_fix_olm_missing_images.md")
repo.index.add([file_name, changelog_file])
repo.index.commit("olm missing images fix")
changelog_file = add_file(repo_dir, "changelog/20250510_fix_watched_list_in_helm.md")
repo.index.add(changelog_file)
repo.index.commit("fix watched list in helm")
repo.create_tag("1.0.1", message="Bug fix release")

## Private search preview and 1.1.0 tag (with changelog fix)
changelog_file = add_file(repo_dir, "changelog/20250523_feature_community_search_preview.md")
repo.index.add(changelog_file)
repo.index.commit("private search preview")
changelog_file = add_file(
repo_dir,
"changelog/20250523_feature_community_search_preview_UPDATED.md",
"changelog/20250523_feature_community_search_preview.md",
)
repo.index.add(changelog_file)
repo.index.commit("add limitations in changelog for private search preview")
repo.create_tag("1.1.0", message="Public search preview release")

## OIDC release and 1.2.0 tag
changelog_file = add_file(repo_dir, "changelog/20250610_feature_oidc.md")
repo.index.add(changelog_file)
repo.index.commit("OIDC integration")
repo.create_tag("1.2.0", message="OIDC integration release")

## Static architecture release and 2.0.0 tag
changelog_file = add_file(repo_dir, "changelog/20250612_breaking_static_as_default.md")
repo.index.add(changelog_file)
repo.index.commit("Static architecture as default")
changelog_file = add_file(repo_dir, "changelog/20250616_feature_om_no_service_mesh.md")
repo.index.add(changelog_file)
repo.index.commit("Ops Manager no service mesh support")
changelog_file_1 = add_file(repo_dir, "changelog/20250620_fix_static_container.md")
changelog_file_2 = add_file(repo_dir, "changelog/20250622_fix_external_access.md")
repo.index.add([changelog_file_1, changelog_file_2])
fix_commit = repo.index.commit("Fixes for static architecture")
changelog_file = add_file(repo_dir, "changelog/20250623_prelude_static.md")
repo.index.add(changelog_file)
repo.index.commit("Release notes prelude for static architecture")
repo.create_tag("2.0.0", message="Static architecture release")

## Create release-1.x branch and backport fix
repo.git.checkout("1.2.0")
release_1_x_branch = repo.create_head("release-1.x").checkout()
repo.git.cherry_pick(fix_commit.hexsha)
repo.create_tag("1.2.1", message="Bug fix release")

## Bug fixes and 2.0.1 tag
repo.git.checkout("master")
file_name = create_new_file(repo_dir, "bugfix-placeholder.go", "Bugfix in go\n")
changelog_file = add_file(repo_dir, "changelog/20250701_fix_placeholder.md")
repo.index.add([file_name, changelog_file])
fix_commit_1 = repo.index.commit("placeholder fix")
changelog_file = add_file(repo_dir, "changelog/20250702_fix_clusterspeclist_validation.md")
repo.index.add(changelog_file)
fix_commit_2 = repo.index.commit("fix clusterspeclist validation")
repo.create_tag("2.0.1", message="Bug fix release")

## Backport fixes to release-1.x branch
repo.git.checkout(release_1_x_branch)
repo.git.cherry_pick(fix_commit_1.hexsha)
repo.git.cherry_pick(fix_commit_2.hexsha)
repo.create_tag("1.2.2", message="Bug fix release")

## Bug fix and 2.0.2 tag
repo.git.checkout("master")
changelog_file = add_file(repo_dir, "changelog/20250707_fix_proxy_env_var.md")
repo.index.add(changelog_file)
fix_commit = repo.index.commit("fix proxy env var validation")
repo.create_tag("2.0.2", message="Bug fix release")

## Backport fixes to release-1.x branch
repo.git.checkout(release_1_x_branch)
repo.git.cherry_pick(fix_commit)
repo.create_tag("1.2.3", message="Bug fix release")

## Static architecture release and 3.0.0 tag
repo.git.checkout("master")
changelog_file_1 = add_file(repo_dir, "changelog/20250710_breaking_mongodbmulti_refactor.md")
changelog_file_2 = add_file(repo_dir, "changelog/20250710_prelude_mongodbmulti_refactor.md")
repo.index.add([changelog_file_1, changelog_file_2])
repo.index.commit("Moved MongoDBMulti into single MongoDB resource")
changelog_file = add_file(repo_dir, "changelog/20250711_feature_public_search.md")
repo.index.add(changelog_file)
repo.index.commit("Public search support")
changelog_file = add_file(repo_dir, "changelog/20250712_fix_mongodbuser_phase.md")
repo.index.add(changelog_file)
fix_commit = repo.index.commit("MongoDBUser phase update fix")
repo.create_tag("3.0.0", message="MongoDBMulti integration with MongoDB resource")

## Create release-2.x branch and backport fix
repo.git.checkout("2.0.2")
release_2_x_branch = repo.create_head("release-2.x").checkout()
repo.git.cherry_pick(fix_commit.hexsha)
repo.create_tag("2.0.3", message="Bug fix release")

## Backport fixes to release-1.x branch
fix_commit = release_2_x_branch.commit
repo.git.checkout(release_1_x_branch)
repo.git.cherry_pick(fix_commit)
repo.create_tag("1.2.4", message="Bug fix release")

return repo


def create_new_file(repo_path: str, file_path: str, file_content: str):
"""Create a new file in the repository."""

file_name = os.path.join(repo_path, file_path)
with open(file_name, "a") as f:
f.write(file_content)

return file_name


def add_file(repo_path: str, src_file_path: str, dst_file_path: str | None = None):
"""Adds a file in the repository path."""

if not dst_file_path:
dst_file_path = src_file_path

dst_path = os.path.join(repo_path, dst_file_path)
src_path = os.path.join("scripts/release/testdata", src_file_path)

return shutil.copy(src_path, dst_path)
Loading