Files
php-curl-class/scripts/make_release.py
T
2026-04-20 21:51:51 -04:00

261 lines
8.7 KiB
Python

import json
import os
import pprint
import subprocess
from copy import copy
from datetime import datetime
from datetime import timezone
from pathlib import Path
from git.repo import Repo
from github import Github
# The owner and repository name. For example, octocat/Hello-World.
GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY", "")
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
GITHUB_REF_NAME = os.getenv("GITHUB_REF_NAME")
CURRENT_FILE = Path(__file__)
ROOT = CURRENT_FILE.parents[1]
CHANGELOG_PATH = ROOT / "CHANGELOG.md"
LIBRARY_FILE_PATH = ROOT / "src/Curl/Curl.php"
def main():
# Find most recent tag and timestamp.
# git for-each-ref --format="%(refname:short) | %(creatordate)" "refs/tags/*"
local_repo = Repo(ROOT)
# Sort the tags by version.
# git tag --list | sort --reverse --version-sort
tags = sorted(
local_repo.tags,
key=lambda tag: list(map(int, tag.name.split("."))),
reverse=True,
)
most_recent_tag = tags[0]
print("most_recent_tag: {}".format(most_recent_tag))
most_recent_tag_datetime = most_recent_tag.commit.committed_datetime
print("most_recent_tag_datetime: {}".format(most_recent_tag_datetime))
g = Github(login_or_token=GITHUB_TOKEN)
# Find merged pull requests since the most recent tag.
github_repo = g.get_repo(GITHUB_REPOSITORY)
recent_pulls = github_repo.get_pulls(
state="closed",
sort="updated",
direction="desc",
)
pull_request_changes = []
# Group pull requests by semantic version change type.
pull_request_by_type = {
"major": [],
"minor": [],
"patch": [],
"cleanup": [],
"unspecified": [],
}
# Track if any pull request is missing a semantic version change type.
pulls_missing_semver_label = []
for pull in recent_pulls:
print("-" * 10)
print("pull.title: {}".format(pull.title))
print("pull.html_url: {}".format(pull.html_url))
# Skip this and remaining pull requests as this one's last update
# predates the most recent tag. Results are sorted by updated desc, so
# no later entry in the list could have been merged after the tag
# either.
pull_updated_at = copy(pull.updated_at).replace(tzinfo=timezone.utc)
print("pull_updated_at: {}".format(pull_updated_at))
if pull_updated_at < most_recent_tag_datetime:
print("stopping: updated_at predates most recent tag")
break
# Skip this pull request as it's not merged.
if not pull.merged:
print("skipping: not merged")
continue
# Skip this pull request as it was merged before the most recent tag.
pull_merged_at = copy(pull.merged_at).replace(tzinfo=timezone.utc)
print("pull_merged_at: {}".format(pull_merged_at))
if pull_merged_at < most_recent_tag_datetime:
print("skipping: merged prior to last release")
continue
pull_labels = {label.name for label in pull.labels}
if "major-incompatible-changes" in pull_labels:
group_name = "major"
elif "minor-backwards-compatible-added-functionality" in pull_labels:
group_name = "minor"
elif "patch-backwards-compatible-bug-fixes" in pull_labels:
group_name = "patch"
elif "cleanup-no-release-required" in pull_labels:
group_name = "cleanup"
else:
group_name = "unspecified"
pulls_missing_semver_label.append(pull)
pull_request_by_type[group_name].append(pull)
# pprint.pprint(pull.title)
# pprint.pprint('most recent: {}'.format(most_recent_tag_datetime))
# pprint.pprint('merged at: {}'.format(pull.merged_at))
# print(pull.html_url)
if group_name in ["major", "minor", "patch"]:
pull_request_changes.append(
"- {} ([#{}]({}))".format(pull.title, pull.number, pull.html_url)
)
# print('-' * 10)
# pprint.pprint(pull_request_changes)
# Raise error if any pull request is missing a semantic version change type.
# Do this check before checking for any pull request changes as all the pull
# requests changes might be missing semver labels.
if pulls_missing_semver_label:
error_message = (
"Merged pull request(s) found without semantic version label:\n"
"{}".format(
"\n".join(
" {}".format(pull.html_url) for pull in pulls_missing_semver_label
)
)
)
raise Exception(error_message)
if not pull_request_changes:
print("No merged pull requests since the most recent tag release were found")
return
# pprint.pprint(pull_request_by_type)
if pull_request_by_type.get("major"):
highest_semantic_version = "major"
php_file_path = "scripts/bump_major_version.php"
elif pull_request_by_type.get("minor"):
highest_semantic_version = "minor"
php_file_path = "scripts/bump_minor_version.php"
elif pull_request_by_type.get("patch"):
highest_semantic_version = "patch"
php_file_path = "scripts/bump_patch_version.php"
else:
highest_semantic_version = None
php_file_path = ""
print("highest_semantic_version: {}".format(highest_semantic_version))
# Bump version and get next semantic version.
command = ["php", php_file_path]
print("running command: {}".format(command))
proc = subprocess.Popen(
command, shell=False, stdout=subprocess.PIPE, stdin=subprocess.PIPE
)
stdout, stderr = proc.communicate()
print("stdout: {}".format(stdout))
print("stderr: {}".format(stderr))
result = json.loads(stdout)
pprint.pprint(result)
release_version = result["new_version"]
today = datetime.today()
print("today: {} (tzinfo={})".format(today, today.tzinfo))
today = today.replace(tzinfo=timezone.utc)
print("today: {} (tzinfo={})".format(today, today.tzinfo))
release_date = today.strftime("%Y-%m-%d")
print("release_date: {}".format(release_date))
release_title = "{} - {}".format(release_version, release_date)
print("release_title: {}".format(release_title))
release_content = "".join(
[
"## {}\n",
"\n",
"{}",
]
).format(release_title, "\n".join(pull_request_changes))
old_content = CHANGELOG_PATH.read_text()
new_content = old_content.replace(
"<!-- CHANGELOG_PLACEHOLDER -->",
"<!-- CHANGELOG_PLACEHOLDER -->\n\n{}".format(release_content),
)
# print(new_content[:800])
CHANGELOG_PATH.write_text(new_content)
# print('git status before adding files:')
# print(local_repo.git.status())
local_repo.git.add(CHANGELOG_PATH)
local_repo.git.add(LIBRARY_FILE_PATH)
# print('git status after adding files:')
# print(local_repo.git.status())
# print('diff:')
# git diff --cached --color=always
# print(local_repo.git.diff(cached=True, color='always'))
local_repo.git.commit(
message=result["message"],
author="{} <{}>".format(
local_repo.git.config("--get", "user.name"),
local_repo.git.config("--get", "user.email"),
),
)
print("diff after commit:")
# git log --max-count=1 --patch --color=always
print(local_repo.git.log(max_count="1", patch=True, color="always"))
# Push local changes.
server = "https://{}@github.com/{}.git".format(GITHUB_TOKEN, GITHUB_REPOSITORY)
print(
'pushing changes to branch "{}" of repository "{}"'.format(
GITHUB_REF_NAME, GITHUB_REPOSITORY
)
)
local_repo.git.push(server, GITHUB_REF_NAME)
# Create tag and release.
tag = result["new_version"]
tag_message = result["message"]
release_name = "Release {}".format(release_version)
release_message = (
"See [change log]"
"(https://github.com/php-curl-class/php-curl-class/blob/master/CHANGELOG.md) for changes.\n"
"\n"
"https://github.com/php-curl-class/php-curl-class/compare/{}...{}".format(
result["old_version"],
result["new_version"],
)
)
commit_sha = local_repo.head.commit.hexsha
print("tag: {}".format(tag))
print('tag_message: "{}"'.format(tag_message))
print('release_name: "{}"'.format(release_name))
print('release_message: "{}"'.format(release_message))
print("commit_sha: {}".format(commit_sha))
github_repo.create_git_tag_and_release(
tag=tag,
tag_message=tag_message,
release_name=release_name,
release_message=release_message,
object=commit_sha,
type="commit",
draft=False,
)
print("created tag and release")
if __name__ == "__main__":
main()