ClickHouse/tests/ci/release.py

693 lines
24 KiB
Python
Raw Normal View History

2022-12-02 17:27:36 +00:00
#!/usr/bin/env python3
2022-02-14 21:48:01 +00:00
2022-11-07 20:30:41 +00:00
"""
script to create releases for ClickHouse
2023-09-14 10:54:17 +00:00
The `gh` CLI preferred over the PyGithub to have an easy way to rollback bad
2022-11-07 20:30:41 +00:00
release in command line by simple execution giving rollback commands
On another hand, PyGithub is used for convenient getting commit's status from API
To run this script on a freshly installed Ubuntu 22.04 system, it is enough to do the following commands:
sudo apt install pip
pip install requests boto3 github PyGithub
sudo snap install gh
gh auth login
2022-11-07 20:30:41 +00:00
"""
2022-02-14 21:48:01 +00:00
import argparse
2022-12-02 17:55:22 +00:00
import json
2022-02-14 21:48:01 +00:00
import logging
import subprocess
from contextlib import contextmanager
from typing import Any, Final, Iterator, List, Optional, Tuple
2022-02-14 21:48:01 +00:00
2024-01-08 12:24:27 +00:00
from git_helper import Git, commit, release_branch
from ci_config import Labels
from report import SUCCESS
2022-02-14 21:48:01 +00:00
from version_helper import (
FILE_WITH_VERSION_PATH,
2022-04-21 15:29:20 +00:00
GENERATED_CONTRIBUTORS,
2022-02-14 21:48:01 +00:00
ClickHouseVersion,
VersionType,
get_abs_path,
get_version_from_repo,
update_cmake_version,
2022-04-21 15:29:20 +00:00
update_contributors,
2022-02-14 21:48:01 +00:00
)
2022-12-02 17:27:36 +00:00
RELEASE_READY_STATUS = "Ready for release"
class Repo:
VALID = ("ssh", "https", "origin")
def __init__(self, repo: str, protocol: str):
self._repo = repo
self._url = ""
self.url = protocol
@property
def url(self) -> str:
return self._url
@url.setter
2022-11-15 12:10:13 +00:00
def url(self, protocol: str) -> None:
if protocol == "ssh":
self._url = f"git@github.com:{self}.git"
elif protocol == "https":
self._url = f"https://github.com/{self}.git"
elif protocol == "origin":
self._url = protocol
else:
2024-02-26 18:25:02 +00:00
raise ValueError(f"protocol must be in {self.VALID}")
def __str__(self):
return self._repo
2022-02-14 21:48:01 +00:00
class Release:
NEW = "new" # type: Final
PATCH = "patch" # type: Final
VALID_TYPE = (NEW, PATCH) # type: Final[Tuple[str, str]]
2022-04-21 15:29:20 +00:00
CMAKE_PATH = get_abs_path(FILE_WITH_VERSION_PATH)
CONTRIBUTORS_PATH = get_abs_path(GENERATED_CONTRIBUTORS)
2022-02-14 21:48:01 +00:00
2022-11-15 12:10:13 +00:00
def __init__(
self,
repo: Repo,
release_commit: str,
release_type: str,
2023-01-26 12:57:06 +00:00
dry_run: bool,
with_stderr: bool,
2022-11-15 12:10:13 +00:00
):
self.repo = repo
2022-02-14 21:48:01 +00:00
self._release_commit = ""
self.release_commit = release_commit
2023-01-26 12:57:06 +00:00
self.dry_run = dry_run
self.with_stderr = with_stderr
assert release_type in self.VALID_TYPE
self.release_type = release_type
2022-12-15 16:43:48 +00:00
self._git = Git()
self._version = get_version_from_repo(git=self._git)
2023-01-26 12:57:06 +00:00
self.release_version = self.version
self._release_branch = ""
self._version_new_tag = None # type: Optional[ClickHouseVersion]
self._rollback_stack = [] # type: List[str]
2022-02-14 21:48:01 +00:00
2023-01-26 12:57:06 +00:00
def run(
self, cmd: str, cwd: Optional[str] = None, dry_run: bool = False, **kwargs: Any
) -> str:
2022-02-16 16:04:25 +00:00
cwd_text = ""
if cwd:
cwd_text = f" (CWD='{cwd}')"
2023-01-26 12:57:06 +00:00
if dry_run:
logging.info("Would run command%s:\n %s", cwd_text, cmd)
return ""
if not self.with_stderr:
kwargs["stderr"] = subprocess.DEVNULL
2023-01-26 12:57:06 +00:00
2022-02-16 16:04:25 +00:00
logging.info("Running command%s:\n %s", cwd_text, cmd)
2022-11-07 20:30:41 +00:00
return self._git.run(cmd, cwd, **kwargs)
2022-02-14 21:48:01 +00:00
2023-01-26 12:57:06 +00:00
def set_release_info(self):
2023-01-09 20:16:10 +00:00
# Fetch release commit and tags in case they don't exist locally
2023-02-15 15:05:07 +00:00
self.run(
f"git fetch {self.repo.url} {self.release_commit} --no-recurse-submodules"
)
self.run(f"git fetch {self.repo.url} --tags --no-recurse-submodules")
# Get the actual version for the commit before check
with self._checkout(self.release_commit, True):
self.release_branch = f"{self.version.major}.{self.version.minor}"
2023-01-26 12:57:06 +00:00
self.release_version = get_version_from_repo(git=self._git)
self.release_version.with_description(self.get_stable_release_type())
self.read_version()
def read_version(self):
2022-02-16 14:59:51 +00:00
self._git.update()
self.version = get_version_from_repo(git=self._git)
2022-02-14 21:48:01 +00:00
def get_stable_release_type(self) -> str:
2023-11-30 13:06:52 +00:00
if self.version.is_lts:
return VersionType.LTS
return VersionType.STABLE
2022-11-07 20:30:41 +00:00
def check_commit_release_ready(self):
2022-12-02 17:55:22 +00:00
per_page = 100
page = 1
while True:
statuses = json.loads(
self.run(
f"gh api 'repos/{self.repo}/commits/{self.release_commit}"
f"/statuses?per_page={per_page}&page={page}'"
)
)
if not statuses:
break
for status in statuses:
if status["context"] == RELEASE_READY_STATUS:
if not status["state"] == SUCCESS:
2024-02-26 18:25:02 +00:00
raise ValueError(
2022-12-02 17:55:22 +00:00
f"the status {RELEASE_READY_STATUS} is {status['state']}"
", not success"
)
2022-11-07 20:30:41 +00:00
return
2022-12-02 17:55:22 +00:00
page += 1
2022-11-07 20:30:41 +00:00
2024-02-26 18:25:02 +00:00
raise KeyError(
2022-11-07 20:30:41 +00:00
f"the status {RELEASE_READY_STATUS} "
f"is not found for commit {self.release_commit}"
)
2022-02-25 10:53:21 +00:00
def check_prerequisites(self):
"""
Check tooling installed in the system, `git` is checked by Git() init
2022-02-25 10:53:21 +00:00
"""
try:
self.run("gh auth status")
except subprocess.SubprocessError:
logging.error(
"The github-cli either not installed or not setup, please follow "
"the instructions on https://github.com/cli/cli#installation and "
"https://cli.github.com/manual/"
)
raise
2022-02-25 10:53:21 +00:00
if self.release_type == self.PATCH:
self.check_commit_release_ready()
2022-11-07 20:30:41 +00:00
def do(
self, check_dirty: bool, check_run_from_master: bool, check_branch: bool
) -> None:
2022-02-25 10:53:21 +00:00
self.check_prerequisites()
2022-02-14 21:48:01 +00:00
2022-02-21 11:44:37 +00:00
if check_dirty:
2022-02-16 14:59:51 +00:00
logging.info("Checking if repo is clean")
try:
self.run("git diff HEAD --exit-code")
except subprocess.CalledProcessError:
logging.fatal("Repo contains uncommitted changes")
raise
if check_run_from_master and self._git.branch != "master":
2024-02-26 18:25:02 +00:00
raise RuntimeError("the script must be launched only from master")
2022-02-14 21:48:01 +00:00
2023-01-26 12:57:06 +00:00
self.set_release_info()
2022-02-21 11:44:37 +00:00
if check_branch:
self.check_branch()
2022-02-16 14:59:51 +00:00
if self.release_type == self.NEW:
2023-01-26 12:57:06 +00:00
with self._checkout(self.release_commit, True):
# Checkout to the commit, it will provide the correct current version
with self.new_release():
with self.create_release_branch():
logging.info(
"Publishing release %s from commit %s is done",
self.release_version.describe,
self.release_commit,
)
2022-02-14 21:48:01 +00:00
elif self.release_type == self.PATCH:
2023-01-26 12:57:06 +00:00
with self._checkout(self.release_commit, True):
with self.patch_release():
logging.info(
"Publishing release %s from commit %s is done",
self.release_version.describe,
self.release_commit,
)
2022-02-18 22:36:40 +00:00
2023-01-26 12:57:06 +00:00
if self.dry_run:
logging.info("Dry running, clean out possible changes")
rollback = self._rollback_stack.copy()
rollback.reverse()
for cmd in rollback:
self.run(cmd)
return
self.log_post_workflows()
self.log_rollback()
2022-02-14 21:48:01 +00:00
def check_no_tags_after(self):
tags_after_commit = self.run(f"git tag --contains={self.release_commit}")
if tags_after_commit:
2024-02-26 18:25:02 +00:00
raise RuntimeError(
2022-02-14 21:48:01 +00:00
f"Commit {self.release_commit} belongs to following tags:\n"
f"{tags_after_commit}\nChoose another commit"
)
def check_branch(self):
branch = self.release_branch
if self.release_type == self.NEW:
2022-02-14 21:48:01 +00:00
# Commit to spin up the release must belong to a main branch
2022-02-18 11:35:24 +00:00
branch = "master"
elif self.release_type != self.PATCH:
raise (
ValueError(f"release_type {self.release_type} not in {self.VALID_TYPE}")
)
# Prefetch the branch to have it updated
2022-05-19 08:05:34 +00:00
if self._git.branch == branch:
2023-02-15 15:05:07 +00:00
self.run("git pull --no-recurse-submodules")
2022-05-19 08:05:34 +00:00
else:
2023-02-15 15:05:07 +00:00
self.run(
f"git fetch {self.repo.url} {branch}:{branch} --no-recurse-submodules"
)
output = self.run(f"git branch --contains={self.release_commit} {branch}")
if branch not in output:
2024-02-26 18:25:02 +00:00
raise RuntimeError(
f"commit {self.release_commit} must belong to {branch} "
f"for {self.release_type} release"
)
2022-02-14 21:48:01 +00:00
def _update_cmake_contributors(
self, version: ClickHouseVersion, reset_tweak: bool = True
) -> None:
if reset_tweak:
2023-02-27 13:18:41 +00:00
desc = version.description
version = version.reset_tweak()
2023-02-27 13:18:41 +00:00
version.with_description(desc)
2023-01-26 12:57:06 +00:00
update_cmake_version(version)
update_contributors(raise_error=True)
if self.dry_run:
logging.info(
"Dry running, resetting the following changes in the repo:\n%s",
self.run(f"git diff '{self.CMAKE_PATH}' '{self.CONTRIBUTORS_PATH}'"),
)
self.run(f"git checkout '{self.CMAKE_PATH}' '{self.CONTRIBUTORS_PATH}'")
def _commit_cmake_contributors(
self, version: ClickHouseVersion, reset_tweak: bool = True
) -> None:
if reset_tweak:
version = version.reset_tweak()
2023-01-26 12:57:06 +00:00
self.run(
f"git commit '{self.CMAKE_PATH}' '{self.CONTRIBUTORS_PATH}' "
f"-m 'Update autogenerated version to {version.string} and contributors'",
2023-01-26 12:57:06 +00:00
dry_run=self.dry_run,
)
@property
def bump_part(self) -> ClickHouseVersion.PART_TYPE:
if self.release_type == Release.NEW:
if self._version.minor >= 12:
return "major"
return "minor"
return "patch"
@property
def has_rollback(self) -> bool:
return bool(self._rollback_stack)
def log_rollback(self):
if self.has_rollback:
2022-08-03 22:24:43 +00:00
rollback = self._rollback_stack.copy()
rollback.reverse()
logging.info(
"To rollback the action run the following commands:\n %s",
"\n ".join(rollback),
)
def log_post_workflows(self):
logging.info(
"To verify all actions are running good visit the following links:\n %s",
"\n ".join(
f"https://github.com/{self.repo}/actions/workflows/{action}.yml"
for action in ("release", "tags_stable")
),
)
2022-02-14 21:48:01 +00:00
@contextmanager
def create_release_branch(self):
2022-02-14 21:48:01 +00:00
self.check_no_tags_after()
# Create release branch
self.read_version()
assert self._version_new_tag is not None
with self._create_tag(
self._version_new_tag.describe,
self.release_commit,
f"Initial commit for release {self._version_new_tag.major}.{self._version_new_tag.minor}",
):
with self._create_branch(self.release_branch, self.release_commit):
with self._checkout(self.release_branch, True):
with self._bump_release_branch():
yield
2022-02-14 21:48:01 +00:00
2022-02-18 22:36:40 +00:00
@contextmanager
def patch_release(self):
2022-02-18 22:36:40 +00:00
self.check_no_tags_after()
self.read_version()
version_type = self.get_stable_release_type()
2022-02-18 22:36:40 +00:00
self.version.with_description(version_type)
with self._create_gh_release(False):
self.version = self.version.update(self.bump_part)
2022-02-18 22:36:40 +00:00
self.version.with_description(version_type)
self._update_cmake_contributors(self.version)
2023-09-14 10:54:17 +00:00
# Checking out the commit of the branch and not the branch itself,
2022-02-18 22:36:40 +00:00
# then we are able to skip rollback
with self._checkout(f"{self.release_branch}^0", False):
2022-02-18 22:36:40 +00:00
current_commit = self.run("git rev-parse HEAD")
self._commit_cmake_contributors(self.version)
2022-02-18 22:36:40 +00:00
with self._push(
"HEAD", with_rollback_on_fail=False, remote_ref=self.release_branch
):
# DO NOT PUT ANYTHING ELSE HERE
# The push must be the last action and mean the successful release
self._rollback_stack.append(
2023-01-26 12:57:06 +00:00
f"{self.dry_run_prefix}git push {self.repo.url} "
2022-02-18 22:36:40 +00:00
f"+{current_commit}:{self.release_branch}"
)
yield
2022-02-14 21:48:01 +00:00
@contextmanager
def new_release(self):
2022-02-14 21:48:01 +00:00
# Create branch for a version bump
self.read_version()
self.version = self.version.update(self.bump_part)
2022-02-14 21:48:01 +00:00
helper_branch = f"{self.version.major}.{self.version.minor}-prepare"
2022-02-16 14:59:51 +00:00
with self._create_branch(helper_branch, self.release_commit):
2022-02-14 21:48:01 +00:00
with self._checkout(helper_branch, True):
with self._bump_version_in_master(helper_branch):
2022-02-14 21:48:01 +00:00
yield
2022-02-16 14:59:51 +00:00
@property
def version(self) -> ClickHouseVersion:
return self._version
@version.setter
2022-11-15 12:10:13 +00:00
def version(self, version: ClickHouseVersion) -> None:
2022-02-16 14:59:51 +00:00
if not isinstance(version, ClickHouseVersion):
raise ValueError(f"version must be ClickHouseVersion, not {type(version)}")
self._version = version
@property
def release_branch(self) -> str:
return self._release_branch
@release_branch.setter
2022-11-15 12:10:13 +00:00
def release_branch(self, branch: str) -> None:
self._release_branch = release_branch(branch)
2022-02-16 14:59:51 +00:00
@property
def release_commit(self) -> str:
return self._release_commit
@release_commit.setter
2022-11-15 12:10:13 +00:00
def release_commit(self, release_commit: str) -> None:
2022-02-16 14:59:51 +00:00
self._release_commit = commit(release_commit)
2022-02-14 21:48:01 +00:00
2023-01-26 12:57:06 +00:00
@property
def dry_run_prefix(self) -> str:
if self.dry_run:
return "# "
return ""
2022-02-14 21:48:01 +00:00
@contextmanager
def _bump_release_branch(self):
2023-09-14 10:54:17 +00:00
# Update only git, original version stays the same
self._git.update()
new_version = self.version.copy()
version_type = self.get_stable_release_type()
pr_labels = f"--label {Labels.RELEASE}"
if version_type == VersionType.LTS:
pr_labels += f" --label {Labels.RELEASE_LTS}"
new_version.with_description(version_type)
self._update_cmake_contributors(new_version)
2023-01-26 12:57:06 +00:00
self._commit_cmake_contributors(new_version)
with self._push(self.release_branch):
with self._create_gh_label(
f"v{self.release_branch}-must-backport", "10dbed"
):
with self._create_gh_label(
f"v{self.release_branch}-affected", "c2bfff"
):
2023-01-26 12:57:06 +00:00
# The following command is rolled back by deleting branch
# in self._push
self.run(
f"gh pr create --repo {self.repo} --title "
f"'Release pull request for branch {self.release_branch}' "
f"--head {self.release_branch} {pr_labels} "
"--body 'This PullRequest is a part of ClickHouse release "
"cycle. It is used by CI system only. Do not perform any "
2023-01-26 12:57:06 +00:00
"changes with it.'",
dry_run=self.dry_run,
)
# Here the release branch part is done.
# We don't create a release itself automatically to have a
# safe window to backport possible bug fixes.
yield
2022-02-14 21:48:01 +00:00
2022-02-16 14:59:51 +00:00
@contextmanager
def _bump_version_in_master(self, helper_branch: str) -> Iterator[None]:
self.read_version()
self.version = self.version.update(self.bump_part)
self.version.with_description(VersionType.TESTING)
self._update_cmake_contributors(self.version)
2023-01-26 12:57:06 +00:00
self._commit_cmake_contributors(self.version)
# Create a version-new tag
self._version_new_tag = self.version.copy()
self._version_new_tag.tweak = 1
self._version_new_tag.with_description(VersionType.NEW)
with self._push(helper_branch):
2022-02-16 14:59:51 +00:00
body_file = get_abs_path(".github/PULL_REQUEST_TEMPLATE.md")
2023-01-26 12:57:06 +00:00
# The following command is rolled back by deleting branch in self._push
2022-02-16 14:59:51 +00:00
self.run(
f"gh pr create --repo {self.repo} --title 'Update version after "
f"release' --head {helper_branch} --body-file '{body_file}' "
2023-01-26 12:57:06 +00:00
"--label 'do not test' --assignee @me",
dry_run=self.dry_run,
2022-02-16 14:59:51 +00:00
)
# Here the new release part is done
2022-02-16 14:59:51 +00:00
yield
@contextmanager
2022-11-15 12:10:13 +00:00
def _checkout(self, ref: str, with_checkout_back: bool = False) -> Iterator[None]:
self._git.update()
2022-02-16 14:59:51 +00:00
orig_ref = self._git.branch or self._git.sha
rollback_cmd = ""
2022-02-16 14:59:51 +00:00
if ref not in (self._git.branch, self._git.sha):
self.run(f"git checkout {ref}")
# checkout is not put into rollback_stack intentionally
rollback_cmd = f"git checkout {orig_ref}"
# always update version and git after checked out ref
self.read_version()
2022-02-16 14:59:51 +00:00
try:
yield
except (Exception, KeyboardInterrupt):
2022-02-16 14:59:51 +00:00
logging.warning("Rolling back checked out %s for %s", ref, orig_ref)
self.run(f"git reset --hard; git checkout -f {orig_ref}")
2022-02-16 14:59:51 +00:00
raise
2024-02-26 18:25:02 +00:00
# Normal flow when we need to checkout back
if with_checkout_back and rollback_cmd:
2024-02-26 18:25:02 +00:00
self.run(rollback_cmd)
2022-02-16 14:59:51 +00:00
@contextmanager
2022-11-15 12:10:13 +00:00
def _create_branch(self, name: str, start_point: str = "") -> Iterator[None]:
2022-02-16 14:59:51 +00:00
self.run(f"git branch {name} {start_point}")
2023-01-26 12:57:06 +00:00
rollback_cmd = f"git branch -D {name}"
self._rollback_stack.append(rollback_cmd)
2022-02-16 14:59:51 +00:00
try:
yield
except (Exception, KeyboardInterrupt):
2022-02-16 14:59:51 +00:00
logging.warning("Rolling back created branch %s", name)
self.run(rollback_cmd)
2022-02-16 14:59:51 +00:00
raise
@contextmanager
2022-11-15 12:10:13 +00:00
def _create_gh_label(self, label: str, color_hex: str) -> Iterator[None]:
# API call, https://docs.github.com/en/rest/reference/issues#create-a-label
self.run(
2023-01-26 12:57:06 +00:00
f"gh api repos/{self.repo}/labels -f name={label} -f color={color_hex}",
dry_run=self.dry_run,
)
rollback_cmd = (
f"{self.dry_run_prefix}gh api repos/{self.repo}/labels/{label} -X DELETE"
)
self._rollback_stack.append(rollback_cmd)
2022-02-16 14:59:51 +00:00
try:
yield
except (Exception, KeyboardInterrupt):
2022-02-16 16:04:25 +00:00
logging.warning("Rolling back label %s", label)
self.run(rollback_cmd)
2022-02-16 16:04:25 +00:00
raise
2022-02-16 14:59:51 +00:00
2022-02-14 21:48:01 +00:00
@contextmanager
2022-11-15 12:10:13 +00:00
def _create_gh_release(self, as_prerelease: bool) -> Iterator[None]:
2024-06-05 11:38:57 +00:00
tag = self.release_version.describe
with self._create_tag(tag, self.release_commit):
2022-02-14 21:48:01 +00:00
# Preserve tag if version is changed
2022-02-18 22:36:40 +00:00
prerelease = ""
if as_prerelease:
prerelease = "--prerelease"
2022-02-14 21:48:01 +00:00
self.run(
2022-05-18 11:10:12 +00:00
f"gh release create {prerelease} --repo {self.repo} "
2023-01-26 12:57:06 +00:00
f"--title 'Release {tag}' '{tag}'",
dry_run=self.dry_run,
)
rollback_cmd = (
f"{self.dry_run_prefix}gh release delete --yes "
f"--repo {self.repo} '{tag}'"
2022-02-14 21:48:01 +00:00
)
self._rollback_stack.append(rollback_cmd)
2022-02-14 21:48:01 +00:00
try:
yield
except (Exception, KeyboardInterrupt):
2022-02-14 21:48:01 +00:00
logging.warning("Rolling back release publishing")
self.run(rollback_cmd)
2022-02-14 21:48:01 +00:00
raise
@contextmanager
2024-06-05 11:38:57 +00:00
def _create_tag(
self, tag: str, commit: str, tag_message: str = ""
) -> Iterator[None]:
tag_message = tag_message or f"Release {tag}"
# Create tag even in dry-run
self.run(f"git tag -a -m '{tag_message}' '{tag}' {commit}")
rollback_cmd = f"git tag -d '{tag}'"
self._rollback_stack.append(rollback_cmd)
2022-02-14 21:48:01 +00:00
try:
2023-01-26 12:57:06 +00:00
with self._push(tag):
2022-02-14 21:48:01 +00:00
yield
except (Exception, KeyboardInterrupt):
2022-02-14 21:48:01 +00:00
logging.warning("Rolling back tag %s", tag)
self.run(rollback_cmd)
2022-02-14 21:48:01 +00:00
raise
@contextmanager
2022-11-15 12:10:13 +00:00
def _push(
self, ref: str, with_rollback_on_fail: bool = True, remote_ref: str = ""
) -> Iterator[None]:
2022-02-18 22:36:40 +00:00
if remote_ref == "":
remote_ref = ref
2023-01-26 12:57:06 +00:00
self.run(f"git push {self.repo.url} {ref}:{remote_ref}", dry_run=self.dry_run)
if with_rollback_on_fail:
2023-01-26 12:57:06 +00:00
rollback_cmd = (
f"{self.dry_run_prefix}git push -d {self.repo.url} {remote_ref}"
)
self._rollback_stack.append(rollback_cmd)
2022-02-14 21:48:01 +00:00
try:
yield
except (Exception, KeyboardInterrupt):
if with_rollback_on_fail:
logging.warning("Rolling back pushed ref %s", ref)
self.run(rollback_cmd)
2022-02-14 21:48:01 +00:00
raise
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Script to release a new ClickHouse version, requires `git` and "
"`gh` (github-cli) commands "
"!!! LAUNCH IT ONLY FROM THE MASTER BRANCH !!!",
2022-02-14 21:48:01 +00:00
)
parser.add_argument(
"--commit",
required=True,
type=commit,
help="commit create a release",
)
2022-02-14 21:48:01 +00:00
parser.add_argument(
"--repo",
default="ClickHouse/ClickHouse",
help="repository to create the release",
)
parser.add_argument(
"--remote-protocol",
"-p",
default="ssh",
choices=Repo.VALID,
help="repo protocol for git commands remote, 'origin' is a special case and "
"uses 'origin' as a remote",
)
2022-02-14 21:48:01 +00:00
parser.add_argument(
"--type",
required=True,
choices=Release.VALID_TYPE,
2022-02-14 21:48:01 +00:00
dest="release_type",
help="a release type to bump the major.minor.patch version part, "
"new branch is created only for the value 'new'",
2022-02-14 21:48:01 +00:00
)
parser.add_argument("--with-release-branch", default=True, help=argparse.SUPPRESS)
2022-02-21 11:44:37 +00:00
parser.add_argument("--check-dirty", default=True, help=argparse.SUPPRESS)
2022-02-14 21:48:01 +00:00
parser.add_argument(
"--no-check-dirty",
2022-02-21 11:44:37 +00:00
dest="check_dirty",
action="store_false",
default=argparse.SUPPRESS,
2023-09-14 10:54:17 +00:00
help="(dangerous) if set, skip check repository for uncommitted changes",
2022-02-14 21:48:01 +00:00
)
parser.add_argument("--check-run-from-master", default=True, help=argparse.SUPPRESS)
parser.add_argument(
"--no-run-from-master",
dest="check_run_from_master",
action="store_false",
default=argparse.SUPPRESS,
help="(for development) if set, the script could run from non-master branch",
)
2022-02-21 11:44:37 +00:00
parser.add_argument("--check-branch", default=True, help=argparse.SUPPRESS)
2022-02-14 21:48:01 +00:00
parser.add_argument(
"--no-check-branch",
2022-02-21 11:44:37 +00:00
dest="check_branch",
action="store_false",
default=argparse.SUPPRESS,
help="(debug or development only, dangerous) if set, skip the branch check for "
"a run. By default, 'new' type work only for master, and 'patch' "
"works only for a release branches, that name "
"should be the same as '$MAJOR.$MINOR' version, e.g. 22.2",
2022-02-14 21:48:01 +00:00
)
2023-01-26 12:57:06 +00:00
parser.add_argument(
"--dry-run",
action="store_true",
help="do not make any actual changes in the repo, just show what will be done",
)
parser.add_argument(
"--with-stderr",
action="store_true",
help="if set, the stderr of all subprocess commands will be printed as well",
)
2022-02-14 21:48:01 +00:00
return parser.parse_args()
def main():
logging.basicConfig(level=logging.INFO)
args = parse_args()
repo = Repo(args.repo, args.remote_protocol)
release = Release(
repo, args.commit, args.release_type, args.dry_run, args.with_stderr
)
2022-02-14 21:48:01 +00:00
try:
release.do(args.check_dirty, args.check_run_from_master, args.check_branch)
except:
if release.has_rollback:
logging.error(
"!!The release process finished with error, read the output carefully!!"
)
logging.error(
"Probably, rollback finished with error. "
"If you don't see any of the following commands in the output, "
"execute them manually:"
)
release.log_rollback()
raise
2022-02-14 21:48:01 +00:00
if __name__ == "__main__":
main()