Skip to content

Commit

Permalink
feat: add changelog generation and version update
Browse files Browse the repository at this point in the history
  • Loading branch information
SafetyQuincyF committed Oct 23, 2024
1 parent 212318c commit 7da6fd6
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 3 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is partly based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and [PEP 440](https://peps.python.org/pep-0440/)

## [3.2.9] - 2024-10-23
- chore: deprection-message-for-license-command (4149b70)
- feat: add-pull-request-template (#604) (61b2fe2)
- fix: devcontainer fix (be42d8e)
- fix: safety error when scan is run without being authed (5ec80dd)
- feat: add-devcontainers-support (0591838)
- fix: internal-server-error (04d7efb)
- fix: clarify-vulnerabilities-found/ Fixed the issue where the vulnerabilities (07bc5b7)
- chore: added check arg depreciation warning (78109e5)
- feature: release-script: add release script (#602) (cc49542)

## [3.2.8] - 2024-09-27
- feat: enhance version comparison logic for check-updates command (#605)
- docs: add demo Jupyter Notebook (#601)
Expand Down
2 changes: 1 addition & 1 deletion safety/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.8
3.2.9
147 changes: 147 additions & 0 deletions scripts/release_scripts/increment_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import subprocess
import re
from datetime import date
import sys


def get_current_version(file_path: str) -> str:
"""Read the current version from the VERSION file."""
with open(file_path, "r") as file:
return file.read().strip()


def get_last_version_from_changelog(file_path: str) -> str:
"""Extract the last version noted in the CHANGELOG.MD file."""
with open(file_path, "r") as file:
content = file.read()
match = re.search(r"\[(\d+\.\d+\.\d+)\] - \d{4}-\d{2}-\d{2}", content)
return match.group(1) if match else None


def increment_version(version: str, bump_type: str) -> str:
"""Increment the version number based on the bump type."""
major, minor, patch = map(int, version.split("."))

if bump_type == "major":
major += 1
minor = 0
patch = 0
elif bump_type == "minor":
minor += 1
patch = 0
elif bump_type == "patch":
patch += 1
else:
raise ValueError("Invalid bump type. Use 'major', 'minor', or 'patch'.")

return f"{major}.{minor}.{patch}"


def update_version_file(file_path: str, new_version: str):
"""Update the VERSION file with the new version."""
with open(file_path, "w") as file:
file.write(new_version)


def get_git_commits_since_last_version(last_version: str) -> str:
"""Get all git commits since the last version."""
result = subprocess.run(
["git", "log", f"{last_version}..HEAD", "--pretty=format:%s (%h)"],
capture_output=True,
text=True,
)
if result.returncode != 0:
raise RuntimeError(f"Error running git log: {result.stderr}")
return result.stdout.strip()


def format_commit_message(commit_message: str) -> str:
"""Format the commit message according to the changelog format."""
if "Merge pull request" in commit_message:
return None

# Replace only the first occurrence of '/' with a ':'
slash_index = commit_message.find("/")
if slash_index != -1:
commit_message = (
commit_message[:slash_index] + ": " + commit_message[slash_index + 1 :]
)
return f"- {commit_message}"
else:
return f"- fix: {commit_message}"


def update_changelog(file_path: str, new_version: str, new_commits: str):
"""Add new version and commits to the changelog file in the correct section."""
today = date.today().strftime("%Y-%m-%d")
formatted_commits = "\n".join(
formatted_commit
for commit in new_commits.split("\n")
if (formatted_commit := format_commit_message(commit))
)

new_changelog_entry = f"## [{new_version}] - {today}\n{formatted_commits}\n"

with open(file_path, "r+") as file:
content = file.read()

if "[PEP 440](https://peps.python.org/pep-0440/)" in content:
content = content.replace(
"[PEP 440](https://peps.python.org/pep-0440/)",
"[PEP 440](https://peps.python.org/pep-0440/)\n",
)

changelog_header_index = content.find("# Changelog")
if changelog_header_index == -1:
raise Exception("Changelog file is missing the '# Changelog' header")

insertion_point = content.find("\n## [", changelog_header_index)
if insertion_point == -1:
insertion_point = len(content)

updated_content = (
content[:insertion_point] + new_changelog_entry + content[insertion_point:]
)

file.seek(0)
file.write(updated_content)
file.truncate()


def main():
if len(sys.argv) < 2:
print("Usage: python script.py <bump_type>")
print("bump_type: major, minor, or patch")
return

bump_type = sys.argv[1]
version_file = "safety/VERSION"
changelog_file = "CHANGELOG.md"

# Get current version and last version in changelog
current_version = get_current_version(version_file)
last_version = get_last_version_from_changelog(changelog_file)

if not last_version:
print("No previous version found in changelog.")
return

# Increment the current version based on the bump type
new_version = increment_version(current_version, bump_type)

# Update the version file with the new version
update_version_file(version_file, new_version)

# Get git commits since the last version
commits = get_git_commits_since_last_version(last_version)
if not commits:
print("No new commits since the last version.")
return

# Update the changelog with new version and commits
update_changelog(changelog_file, new_version, commits)
print(f"CHANGELOG.MD updated with version {new_version}")


if __name__ == "__main__":
main()
4 changes: 2 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from safety import cli
from safety.models import CVE, SafetyRequirement, Severity, Vulnerability
from safety.util import Package, SafetyContext
from safety.util import Package, SafetyContext, get_safety_version
from safety.auth.models import Auth
from safety_schemas.models.base import AuthenticationType

Expand Down Expand Up @@ -533,7 +533,7 @@ def test_debug_flag(self, mock_get_auth_info, mock_is_valid, mock_get_auth_type,
assert result.exit_code == 0, (
f"CLI exited with code {result.exit_code} and output: {result.output} and error: {result.stderr}"
)
expected_output_snippet = "Safety 3.2.8 scanning"
expected_output_snippet = f"{get_safety_version()} scanning"
assert expected_output_snippet in result.output, (
f"Expected output to contain: {expected_output_snippet}, but got: {result.output}"
)
Expand Down

0 comments on commit 7da6fd6

Please sign in to comment.