305 lines
9.9 KiB
Python
305 lines
9.9 KiB
Python
"""Logic for cutting releases."""
|
|
|
|
import click
|
|
|
|
from .model import Version, Branch, BranchType
|
|
from .project import Project, EsphomeProject, EsphomeDocsProject, EsphomeIssuesProject
|
|
from .util import (
|
|
update_local_copies,
|
|
gprint,
|
|
copy_clipboard,
|
|
open_vscode,
|
|
confirm,
|
|
)
|
|
from .exceptions import EsphomeReleaseError
|
|
from . import changelog
|
|
from . import docs
|
|
|
|
METADATA_MD = """
|
|
<details>
|
|
<summary>Metadata</summary>
|
|
|
|
@coderabbitai ignore
|
|
</details>
|
|
"""
|
|
|
|
|
|
def _bump_branch_name(version: Version) -> str:
|
|
return f"bump-{version}"
|
|
|
|
|
|
def _strategy_merge(project: Project, version: Version, *, base: Branch, head: Branch):
|
|
branch_name = _bump_branch_name(version)
|
|
|
|
project.checkout(base)
|
|
project.checkout_new_branch(branch_name)
|
|
project.merge(head, strategy_option="theirs")
|
|
project.bump_version(version)
|
|
|
|
|
|
def _strategy_cherry_pick(project: Project, version: Version, *, base: Branch):
|
|
branch_name = _bump_branch_name(version)
|
|
milestone = project.get_milestone_by_title(str(version))
|
|
|
|
project.checkout(base)
|
|
project.checkout_new_branch(branch_name)
|
|
ret = project.cherry_pick_from_milestone(milestone)
|
|
project.bump_version(version)
|
|
return ret
|
|
|
|
|
|
def _create_prs(*, version: Version, base: Version, target_branch: BranchType):
|
|
branch_name = _bump_branch_name(version)
|
|
|
|
for proj in [EsphomeProject, EsphomeDocsProject]:
|
|
changelog_md = changelog.generate(
|
|
project=proj,
|
|
base=f"{base}",
|
|
base_version=base,
|
|
head=branch_name,
|
|
head_version=version,
|
|
markdown=True,
|
|
with_sections=True,
|
|
# Don't include author to not spam everybody for release PRs
|
|
include_author=False,
|
|
)
|
|
|
|
body = (
|
|
"**Do not merge, release script will automatically merge**\n"
|
|
+ changelog_md
|
|
+ METADATA_MD
|
|
)
|
|
with proj.workon(branch_name):
|
|
proj.create_pr(title=str(version), target_branch=target_branch, body=body)
|
|
|
|
|
|
def _update_milestones(*, version: Version, next_version: Version):
|
|
for proj in [EsphomeProject, EsphomeDocsProject, EsphomeIssuesProject]:
|
|
proj.create_milestone(str(next_version))
|
|
|
|
old_milestone = proj.get_milestone_by_title(str(version))
|
|
if old_milestone is not None:
|
|
old_milestone.update(state="closed")
|
|
|
|
|
|
def _mark_cherry_picked(cherry_picked):
|
|
for picked in cherry_picked:
|
|
picked.add_labels("cherry-picked")
|
|
|
|
|
|
def _prompt_base_version() -> Version:
|
|
base_str = click.prompt(
|
|
"Please enter base (what release to compare with for changelog)",
|
|
default=str(EsphomeProject.latest_release()),
|
|
)
|
|
return Version.parse(base_str)
|
|
|
|
|
|
def _docs_insert_changelog(*, version: Version, base: Version):
|
|
branch_name = _bump_branch_name(version)
|
|
with EsphomeDocsProject.workon(branch_name):
|
|
changelog_rst = changelog.generate(
|
|
project=EsphomeProject,
|
|
base=f"{base}",
|
|
base_version=base,
|
|
head=branch_name,
|
|
head_version=version,
|
|
markdown=False,
|
|
with_sections=version.beta <= 1,
|
|
)
|
|
|
|
from sys import platform
|
|
|
|
if platform == "darwin":
|
|
copy_clipboard(changelog_rst)
|
|
gprint("Changelog has been copied to your clipboard. Please paste it in.")
|
|
else:
|
|
# Alternative where pbcopy does not work
|
|
gprint("Start Changelog:")
|
|
print(changelog_rst)
|
|
gprint("End Changelog, Please copy and paste changelog")
|
|
changelog_version = version.replace(patch=0, beta=0, dev=False)
|
|
changelog_path = (
|
|
EsphomeDocsProject.path / "changelog" / f"{changelog_version}.rst"
|
|
)
|
|
changelog_index_path = EsphomeDocsProject.path / "changelog" / "index.rst"
|
|
open_vscode(str(changelog_path))
|
|
message = "Pasted changelog"
|
|
if version.beta == 1:
|
|
open_vscode(str(changelog_index_path))
|
|
message += " and updated index"
|
|
message += "?"
|
|
confirm(message)
|
|
EsphomeDocsProject.commit(f"Update changelog for {version}")
|
|
|
|
|
|
def _docs_update_supporters(*, version: Version):
|
|
branch_name = _bump_branch_name(version)
|
|
gprint("Updating supporters")
|
|
with EsphomeDocsProject.workon(branch_name):
|
|
docs.gen_supporters()
|
|
EsphomeDocsProject.commit(f"Update supporters for {version}")
|
|
|
|
|
|
def cut_beta_release(version: Version):
|
|
if not version.beta:
|
|
raise EsphomeReleaseError("Must be beta release!")
|
|
|
|
base = _prompt_base_version()
|
|
update_local_copies()
|
|
|
|
# Commits that were cherry-picked
|
|
cherry_picked = []
|
|
|
|
if version.beta == 1:
|
|
gprint("Creating first beta version using merge")
|
|
dev_str = click.prompt(
|
|
"Please enter next dev version (what will be seen on dev branches after release)",
|
|
default=str(version.next_dev_version),
|
|
)
|
|
dev = Version.parse(dev_str)
|
|
|
|
for proj in [EsphomeProject, EsphomeDocsProject]:
|
|
_strategy_merge(proj, version, base=Branch.BETA, head=Branch.DEV)
|
|
|
|
gprint(f"Updating dev version number to {dev}")
|
|
with proj.workon(Branch.DEV):
|
|
proj.bump_version(dev)
|
|
else:
|
|
gprint("Creating next beta version using cherry-pick")
|
|
for proj in [EsphomeProject, EsphomeDocsProject]:
|
|
cherry_picked.extend(_strategy_cherry_pick(proj, version, base=Branch.BETA))
|
|
_docs_insert_changelog(version=version, base=base)
|
|
_docs_update_supporters(version=version)
|
|
|
|
_confirm_correct()
|
|
_create_prs(version=version, base=base, target_branch=Branch.BETA)
|
|
_update_milestones(version=version, next_version=version.next_beta_version)
|
|
_mark_cherry_picked(cherry_picked)
|
|
|
|
if version.beta == 1:
|
|
for proj in [EsphomeProject, EsphomeDocsProject]:
|
|
with proj.workon(Branch.DEV):
|
|
proj.push()
|
|
|
|
|
|
def cut_release(version: Version):
|
|
if version.beta or version.dev:
|
|
raise EsphomeReleaseError("Must be full release!")
|
|
|
|
base = _prompt_base_version()
|
|
update_local_copies()
|
|
|
|
# Commits that were cherry-picked
|
|
cherry_picked = []
|
|
|
|
if version.patch == 0:
|
|
gprint("Creating first release version using merge")
|
|
for proj in [EsphomeProject, EsphomeDocsProject]:
|
|
_strategy_merge(proj, version, base=Branch.STABLE, head=Branch.BETA)
|
|
else:
|
|
gprint("Creating next full release using cherry-pick")
|
|
for proj in [EsphomeProject, EsphomeDocsProject]:
|
|
cherry_picked.extend(
|
|
_strategy_cherry_pick(proj, version, base=Branch.STABLE)
|
|
)
|
|
_docs_insert_changelog(version=version, base=base)
|
|
_docs_update_supporters(version=version)
|
|
|
|
_confirm_correct()
|
|
_create_prs(version=version, base=base, target_branch=Branch.STABLE)
|
|
_update_milestones(version=version, next_version=version.next_patch_version)
|
|
_mark_cherry_picked(cherry_picked)
|
|
|
|
|
|
def _merge_release_pr(*, proj: Project, version: Version, head_branch: BranchType):
|
|
prs = proj.get_pr_by_title(
|
|
title=str(version), head=_bump_branch_name(version), base=head_branch
|
|
)
|
|
release_pr = None
|
|
if not prs:
|
|
confirm(
|
|
f"No release PRs found for {proj.shortname}, please verify it has been merged."
|
|
)
|
|
elif len(prs) == 1:
|
|
release_pr = prs[0]
|
|
else:
|
|
gprint("Found multiple release PRs. Please select the matchin one")
|
|
for i, pr in enumerate(prs, start=1):
|
|
gprint(f" [{i}] #{pr.number} by @{pr.user.login} ({pr.html_url})")
|
|
gprint(f" [{len(prs)+1}] Auto-merge none")
|
|
num = (
|
|
int(
|
|
click.prompt(
|
|
f"Please select release PR for {proj.shortname}",
|
|
type=click.Choice([i + 1 for i in range(len(prs))]),
|
|
)
|
|
)
|
|
- 1
|
|
)
|
|
release_pr = None if num == len(prs) else prs[num]
|
|
|
|
if release_pr is not None and release_pr.state == "open":
|
|
success = release_pr.merge(merge_method="merge")
|
|
if not success:
|
|
confirm("Merging failed, please check and confirm when ready")
|
|
|
|
|
|
def _publish_release(
|
|
*, version: Version, base: Version, head_branch: BranchType, prerelease: bool
|
|
):
|
|
update_local_copies()
|
|
confirm(f"Publish version {version}?")
|
|
for proj in [EsphomeProject, EsphomeDocsProject]:
|
|
changelog_md = changelog.generate(
|
|
project=proj,
|
|
base=f"{base}",
|
|
base_version=base,
|
|
head=_bump_branch_name(version),
|
|
head_version=version,
|
|
markdown=True,
|
|
with_sections=(version.patch == 0 and version.beta == 0),
|
|
)
|
|
_merge_release_pr(proj=proj, version=version, head_branch=head_branch)
|
|
with proj.workon(head_branch):
|
|
proj.pull()
|
|
proj.create_release(version, prerelease=prerelease, body=changelog_md)
|
|
|
|
|
|
def publish_beta_release(version: Version):
|
|
if not version.beta:
|
|
raise EsphomeReleaseError("Must be beta release!")
|
|
|
|
base = _prompt_base_version()
|
|
_publish_release(
|
|
version=version, base=base, head_branch=Branch.BETA, prerelease=True
|
|
)
|
|
for proj in [EsphomeProject, EsphomeDocsProject]:
|
|
with proj.workon(Branch.DEV):
|
|
proj.pull()
|
|
proj.merge(Branch.BETA, "ours")
|
|
proj.push()
|
|
|
|
|
|
def publish_release(version: Version):
|
|
if version.beta or version.dev:
|
|
raise EsphomeReleaseError("Must be full release!")
|
|
|
|
base = _prompt_base_version()
|
|
_publish_release(
|
|
version=version, base=base, head_branch=Branch.STABLE, prerelease=False
|
|
)
|
|
for proj in [EsphomeProject, EsphomeDocsProject]:
|
|
with proj.workon(Branch.BETA):
|
|
proj.pull()
|
|
proj.merge(Branch.STABLE, "ours")
|
|
proj.push()
|
|
with proj.workon(Branch.DEV):
|
|
proj.pull()
|
|
proj.merge(Branch.STABLE, "ours")
|
|
proj.push()
|
|
|
|
|
|
def _confirm_correct():
|
|
confirm(click.style("Please confirm everything is correct", fg="red"))
|