ClickHouse/tests/ci/git_helper.py

199 lines
6.2 KiB
Python
Raw Normal View History

2022-01-28 13:39:23 +00:00
#!/usr/bin/env python
2022-01-31 15:58:31 +00:00
import argparse
2022-07-14 18:57:03 +00:00
import logging
2022-01-28 13:39:23 +00:00
import os.path as p
import re
import subprocess
2022-11-10 15:40:52 +00:00
from typing import Any, List, Optional
2022-01-28 13:39:23 +00:00
2022-07-14 18:57:03 +00:00
logger = logging.getLogger(__name__)
2022-02-21 09:19:59 +00:00
# ^ 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}[.][1-9]\d*[.][1-9]\d*[.][1-9]\d*-(testing|prestable|stable|lts)\Z"
)
SHA_REGEXP = re.compile(r"\A([0-9]|[a-f]){40}\Z")
2022-01-28 13:39:23 +00:00
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'"
)
2022-01-28 13:39:23 +00:00
# Py 3.8 removeprefix and removesuffix
2022-11-10 15:40:52 +00:00
def removeprefix(string: str, prefix: str) -> str:
2022-01-28 13:39:23 +00:00
if string.startswith(prefix):
return string[len(prefix) :] # noqa: ignore E203, false positive
return string
2022-11-10 15:40:52 +00:00
def removesuffix(string: str, suffix: str) -> str:
2022-01-28 13:39:23 +00:00
if string.endswith(suffix):
return string[: -len(suffix)]
return string
2022-11-10 15:40:52 +00:00
def commit(name: str) -> str:
if not SHA_REGEXP.match(name):
2022-01-31 15:58:31 +00:00
raise argparse.ArgumentTypeError(
"commit hash should contain exactly 40 hex characters"
)
return name
2022-11-10 15:40:52 +00:00
def release_branch(name: str) -> str:
2022-02-18 12:26:55 +00:00
r = re.compile(RELEASE_BRANCH_REGEXP)
if not r.match(name):
raise argparse.ArgumentTypeError("release branch should be as 12.1")
return name
2022-01-28 13:39:23 +00:00
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
2022-01-28 13:39:23 +00:00
2022-11-10 15:40:52 +00:00
def run(self, cmd: str, cwd: Optional[str] = None, **kwargs: Any) -> str:
2022-01-28 13:39:23 +00:00
if cwd is None:
cwd = self.cwd
2022-07-14 18:57:03 +00:00
logger.debug("Running command: %s", cmd)
2022-11-10 15:40:52 +00:00
output = str(
subprocess.check_output(
cmd, shell=True, cwd=cwd, encoding="utf-8", **kwargs
).strip()
)
return output
2022-01-28 13:39:23 +00:00
@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
2022-11-10 15:40:52 +00:00
def cwd(self, value: str) -> None:
# Set _cwd only once, then set it to readonly
if self._cwd != CWD:
return
self._cwd = value
2022-07-14 18:57:03 +00:00
def __call__(self, *args, **kwargs):
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()
2022-01-28 13:39:23 +00:00
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):
self.root = git_runner.cwd
self._ignore_no_tags = ignore_no_tags
self.run = git_runner.run
self.latest_tag = ""
self.new_tag = ""
2022-01-28 13:39:23 +00:00
self.new_branch = ""
self.branch = ""
self.sha = ""
self.sha_short = ""
self.description = "shallow-checkout"
2022-01-28 13:39:23 +00:00
self.commits_since_tag = 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")
2022-07-14 18:57:03 +00:00
self.branch = self.run("git branch --show-current") or self.sha
2022-01-28 13:39:23 +00:00
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)
2022-01-28 13:39:23 +00:00
# Format should be: {latest_tag}-{commits_since_tag}-g{sha_short}
self.description = self.run("git describe --tags --long")
self.commits_since_tag = int(
self.run(f"git rev-list {self.latest_tag}..HEAD --count")
)
@staticmethod
2022-11-10 15:40:52 +00:00
def check_tag(value: str) -> None:
2022-01-28 13:39:23 +00:00
if value == "":
return
if not Git._tag_pattern.match(value):
raise ValueError(f"last tag {value} doesn't match the pattern")
2022-01-28 13:39:23 +00:00
@property
def latest_tag(self) -> str:
return self._latest_tag
@latest_tag.setter
2022-11-10 15:40:52 +00:00
def latest_tag(self, value: str) -> None:
2022-03-31 20:18:14 +00:00
self.check_tag(value)
2022-01-28 13:39:23 +00:00
self._latest_tag = value
@property
def new_tag(self) -> str:
return self._new_tag
@new_tag.setter
2022-11-10 15:40:52 +00:00
def new_tag(self, value: str) -> None:
2022-03-31 20:18:14 +00:00
self.check_tag(value)
2022-01-28 13:39:23 +00:00
self._new_tag = value
@property
def tweak(self) -> int:
if not self.latest_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 not self.commits_since_tag:
# We are in a tagged commit. The tweak should match the
# current version's value
version = self.latest_tag.split("-", maxsplit=1)[0]
try:
return int(version.split(".")[-1])
except ValueError:
# There are no tags, or a wrong tag. Return default
return TWEAK
return self.commits_since_tag
2022-01-28 13:39:23 +00:00
version = self.latest_tag.split("-", maxsplit=1)[0]
return int(version.split(".")[-1]) + self.commits_since_tag