mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-22 07:31:57 +00:00
245 lines
8.2 KiB
Python
245 lines
8.2 KiB
Python
#!/usr/bin/env python
|
|
import argparse
|
|
import logging
|
|
import os.path as p
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
from typing import Any, List, Literal, Optional
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# ^ and $ match subline in `multiple\nlines`
|
|
# \A and \Z match only start and end of the whole string
|
|
RELEASE_BRANCH_REGEXP = r"\A\d+[.]\d+\Z"
|
|
TAG_REGEXP = (
|
|
r"\Av\d{2}" # First two digits of major part
|
|
r"([.][1-9]\d*){3}" # minor.patch.tweak parts
|
|
r"-(new|testing|prestable|stable|lts)\Z" # suffix with a version type
|
|
)
|
|
SHA_REGEXP = re.compile(r"\A([0-9]|[a-f]){40}\Z")
|
|
|
|
CWD = p.dirname(p.realpath(__file__))
|
|
TWEAK = 1
|
|
|
|
GIT_PREFIX = ( # All commits to remote are done as robot-clickhouse
|
|
"git -c user.email=robot-clickhouse@users.noreply.github.com "
|
|
"-c user.name=robot-clickhouse -c commit.gpgsign=false "
|
|
"-c core.sshCommand="
|
|
"'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'"
|
|
)
|
|
|
|
|
|
# Py 3.8 removeprefix and removesuffix
|
|
def removeprefix(string: str, prefix: str) -> str:
|
|
if string.startswith(prefix):
|
|
return string[len(prefix) :] # noqa: ignore E203, false positive
|
|
return string
|
|
|
|
|
|
def removesuffix(string: str, suffix: str) -> str:
|
|
if string.endswith(suffix):
|
|
return string[: -len(suffix)]
|
|
return string
|
|
|
|
|
|
def commit(name: str) -> str:
|
|
if not SHA_REGEXP.match(name):
|
|
raise argparse.ArgumentTypeError(
|
|
"commit hash should contain exactly 40 hex characters"
|
|
)
|
|
return name
|
|
|
|
|
|
def release_branch(name: str) -> str:
|
|
r = re.compile(RELEASE_BRANCH_REGEXP)
|
|
if not r.match(name):
|
|
raise argparse.ArgumentTypeError("release branch should be as 12.1")
|
|
return name
|
|
|
|
|
|
class Runner:
|
|
"""lightweight check_output wrapper with stripping last NEW_LINE"""
|
|
|
|
def __init__(self, cwd: str = CWD, set_cwd_to_git_root: bool = False):
|
|
self._cwd = cwd
|
|
# delayed set cwd to the repo's root, to not do it at the import stage
|
|
self._git_root = None # type: Optional[str]
|
|
self._set_cwd_to_git_root = set_cwd_to_git_root
|
|
|
|
def run(self, cmd: str, cwd: Optional[str] = None, **kwargs: Any) -> str:
|
|
if cwd is None:
|
|
cwd = self.cwd
|
|
logger.debug("Running command: %s", cmd)
|
|
output = str(
|
|
subprocess.check_output(
|
|
cmd, shell=True, cwd=cwd, encoding="utf-8", **kwargs
|
|
).strip()
|
|
)
|
|
return output
|
|
|
|
@property
|
|
def cwd(self) -> str:
|
|
if self._set_cwd_to_git_root:
|
|
if self._git_root is None:
|
|
self._git_root = p.realpath(
|
|
p.join(self._cwd, self.run("git rev-parse --show-cdup", self._cwd))
|
|
)
|
|
return self._git_root
|
|
return self._cwd
|
|
|
|
@cwd.setter
|
|
def cwd(self, value: str) -> None:
|
|
# Set _cwd only once, then set it to readonly
|
|
if self._cwd != CWD:
|
|
return
|
|
self._cwd = value
|
|
|
|
def __call__(self, *args: Any, **kwargs: Any) -> str:
|
|
return self.run(*args, **kwargs)
|
|
|
|
|
|
git_runner = Runner(set_cwd_to_git_root=True)
|
|
|
|
|
|
def is_shallow() -> bool:
|
|
return git_runner.run("git rev-parse --is-shallow-repository") == "true"
|
|
|
|
|
|
def get_tags() -> List[str]:
|
|
if is_shallow():
|
|
raise RuntimeError("attempt to run on a shallow repository")
|
|
return git_runner.run("git tag").split()
|
|
|
|
|
|
class Git:
|
|
"""A small wrapper around subprocess to invoke git commands"""
|
|
|
|
_tag_pattern = re.compile(TAG_REGEXP)
|
|
|
|
def __init__(self, ignore_no_tags: bool = False):
|
|
"""
|
|
new_tag is used for special v24.1.1.1-new tags where the previous version is moved to the release branch
|
|
* 66666666666 Some another commit with version 24.8.1.xxxxx-testing, tweak is counted from new_tag = v24.8.1.1-new
|
|
| * 55555555555 (tag: v24.7.1.123123123-stable, branch: 24.7) tweak counted from new_tag = v24.7.1.1-new
|
|
|/
|
|
* 44444444444 (tag: v24.8.1.1-new)
|
|
| * 33333333333 (tag: v24.6.1.123123123-stable, branch: 24.6) tweak counted from new_tag = v24.6.1.1-new
|
|
|/
|
|
* 22222222222 (tag: v24.7.1.1-new)
|
|
| * 11111111111 (tag: v24.5.1.123123123-stable, branch: 24.5) tweak counted from new_tag = v24.4.1.2088-stable
|
|
|/
|
|
* 00000000000 (tag: v24.6.1.1-new)
|
|
* 6d4b31322d1 (tag: v24.4.1.2088-stable)
|
|
* 2c5c589a882 (tag: v24.3.1.2672-lts)
|
|
* 891689a4150 (tag: v24.2.1.2248-stable)
|
|
* 5a024dfc093 (tag: v24.1.1.2048-stable)
|
|
* a2faa65b080 (tag: v23.12.1.1368-stable)
|
|
* 05bc8ef1e02 (tag: v23.11.1.2711-stable)
|
|
"""
|
|
self.root = git_runner.cwd
|
|
self._ignore_no_tags = ignore_no_tags
|
|
self.run = git_runner.run
|
|
self.latest_tag = ""
|
|
self.new_tag = ""
|
|
self.branch = ""
|
|
self.sha = ""
|
|
self.sha_short = ""
|
|
self.commits_since_latest = 0
|
|
self.commits_since_new = 0
|
|
self.update()
|
|
|
|
def update(self):
|
|
"""Is used to refresh all attributes after updates, e.g. checkout or commit"""
|
|
self.sha = self.run("git rev-parse HEAD")
|
|
self.branch = self.run("git branch --show-current") or self.sha
|
|
self.sha_short = self.sha[:11]
|
|
# The following command shows the most recent tag in a graph
|
|
# Format should match TAG_REGEXP
|
|
if self._ignore_no_tags and is_shallow():
|
|
try:
|
|
self._update_tags(True)
|
|
except subprocess.CalledProcessError:
|
|
pass
|
|
|
|
return
|
|
self._update_tags()
|
|
|
|
def _update_tags(self, suppress_stderr: bool = False) -> None:
|
|
stderr = subprocess.DEVNULL if suppress_stderr else None
|
|
self.latest_tag = self.run("git describe --tags --abbrev=0", stderr=stderr)
|
|
# Format should be: {latest_tag}-{commits_since_tag}-g{sha_short}
|
|
self.commits_since_latest = int(
|
|
self.run(f"git rev-list {self.latest_tag}..HEAD --count")
|
|
)
|
|
if self.latest_tag.endswith("-new"):
|
|
# We won't change the behaviour of the the "latest_tag"
|
|
# So here we set "new_tag" to the previous tag in the graph, that will allow
|
|
# getting alternative "tweak"
|
|
self.new_tag = self.run(
|
|
f"git describe --tags --abbrev=0 --exclude='{self.latest_tag}'",
|
|
stderr=stderr,
|
|
)
|
|
self.commits_since_new = int(
|
|
self.run(f"git rev-list {self.new_tag}..HEAD --count")
|
|
)
|
|
|
|
@staticmethod
|
|
def check_tag(value: str) -> None:
|
|
if value == "":
|
|
return
|
|
if not Git._tag_pattern.match(value):
|
|
raise ValueError(f"last tag {value} doesn't match the pattern")
|
|
|
|
@property
|
|
def latest_tag(self) -> str:
|
|
return self._latest_tag
|
|
|
|
@latest_tag.setter
|
|
def latest_tag(self, value: str) -> None:
|
|
self.check_tag(value)
|
|
self._latest_tag = value
|
|
|
|
@property
|
|
def new_tag(self) -> str:
|
|
return self._new_tag
|
|
|
|
@new_tag.setter
|
|
def new_tag(self, value: str) -> None:
|
|
self.check_tag(value)
|
|
self._new_tag = value
|
|
|
|
@property
|
|
def tweak(self) -> int:
|
|
return self._tweak("latest")
|
|
|
|
@property
|
|
def tweak_to_new(self) -> int:
|
|
return self._tweak("new")
|
|
|
|
def _tweak(self, tag_type: Literal["latest", "new"]) -> int:
|
|
"""Accepts latest or new as a tag_type and returns the tweak number to it"""
|
|
if tag_type == "latest":
|
|
commits = self.commits_since_latest
|
|
tag = self.latest_tag
|
|
else:
|
|
commits = self.commits_since_new
|
|
tag = self.new_tag
|
|
|
|
if not tag.endswith("-testing"):
|
|
# When we are on the tag, we still need to have tweak=1 to not
|
|
# break cmake with versions like 12.13.14.0
|
|
if commits:
|
|
return commits
|
|
# We are in a tagged commit or shallow checkout. The tweak should match the
|
|
# current version's value
|
|
version = tag.split("-", maxsplit=1)[0]
|
|
try:
|
|
return int(version.split(".")[-1])
|
|
except ValueError:
|
|
# There are no tags (shallow checkout), or a wrong tag. Return default
|
|
return TWEAK
|
|
|
|
version = tag.split("-", maxsplit=1)[0]
|
|
return int(version.split(".")[-1]) + commits
|