From 09a8e7ddb2a2952ba28c6b94651a84e77ccb6c0a Mon Sep 17 00:00:00 2001 From: strtgbb <146047128+strtgbb@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:39:54 -0400 Subject: [PATCH 1/4] Stop rewriting VERSION_STRING and VERSION_DESCRIBE so upstream tag merges no longer conflict. Derive the flavoured string in version.cmake, take the release flavour from the tag, and check tags against VERSION_TWEAK plus the flavour track prefix instead of the committed describe line. Co-authored-by: Cursor --- ci/jobs/scripts/clickhouse_version.py | 3 +- ci/jobs/scripts/workflow_hooks/version_log.py | 5 + ci/tests/test_clickhouse_version.py | 161 ++++++++---------- cmake/autogenerated_versions.txt | 4 +- cmake/version.cmake | 10 +- tests/ci/version_helper.py | 34 +++- 6 files changed, 114 insertions(+), 103 deletions(-) diff --git a/ci/jobs/scripts/clickhouse_version.py b/ci/jobs/scripts/clickhouse_version.py index 10e3e0e1809a..f30ab9a1e8d1 100644 --- a/ci/jobs/scripts/clickhouse_version.py +++ b/ci/jobs/scripts/clickhouse_version.py @@ -83,7 +83,8 @@ def _version_type_from_describe(versions: dict) -> str: flavoured versions.""" describe = str(versions.get("describe", "")) flavour = str(versions.get("flavour") or "") - if flavour and describe == f"v{versions.get('string', '')}": + # NOTE (strtgbb): VERSION_DESCRIBE is left as is to avoid merge conflicts + if flavour: return flavour return describe.rsplit("-", 1)[1] if "-" in describe else "" diff --git a/ci/jobs/scripts/workflow_hooks/version_log.py b/ci/jobs/scripts/workflow_hooks/version_log.py index c495558ff970..e7af07cdcea0 100644 --- a/ci/jobs/scripts/workflow_hooks/version_log.py +++ b/ci/jobs/scripts/workflow_hooks/version_log.py @@ -1,3 +1,4 @@ +import os from datetime import datetime from praktika.info import Info @@ -20,6 +21,10 @@ def _build_version(info): version = CHVersion.get_current_version(no_strict=True) if info.pr_number != 0 and not version.flavour: version = version.with_tweak(1) + # NOTE (strtgbb): get the release flavour from the tag, validated with the --check-tag pre-hook + if version.flavour and os.getenv("GITHUB_REF_TYPE") == "tag": + version.flavour = os.getenv("GITHUB_REF_NAME", "").rsplit(".", 1)[-1] + version = version.with_description(version.flavour) return version diff --git a/ci/tests/test_clickhouse_version.py b/ci/tests/test_clickhouse_version.py index 628b5d4cb86f..08717910cbe5 100644 --- a/ci/tests/test_clickhouse_version.py +++ b/ci/tests/test_clickhouse_version.py @@ -1,19 +1,36 @@ +import os +import sys from pathlib import Path +import pytest + from ci.jobs import source_upload from ci.jobs.scripts import clickhouse_version from ci.jobs.scripts.clickhouse_version import CHVersion from ci.jobs.scripts.workflow_hooks import version_log +# `version_helper` imports sibling modules from `tests/ci` by bare name. +_CI_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", "..", "tests", "ci") +) +sys.path.insert(0, _CI_DIR) +try: + from version_helper import check_tag_version # type: ignore[import-not-found] +finally: + sys.path.remove(_CI_DIR) + -FLAVOURED_VERSION_FILE = """\ +# Committed layout: upstream DESCRIBE/STRING, Altinity TWEAK/FLAVOUR appended. +UPSTREAM_BLOCK_VERSION_FILE = """\ SET(VERSION_REVISION 54513) SET(VERSION_MAJOR 26) SET(VERSION_MINOR 8) SET(VERSION_PATCH 1) SET(VERSION_GITHASH 0123456789012345678901234567890123456789) -SET(VERSION_DESCRIBE v26.8.1.10001.altinitytest) -SET(VERSION_STRING 26.8.1.10001.altinitytest) +SET(VERSION_DESCRIBE v26.8.1.1-lts) +SET(VERSION_STRING 26.8.1.1) +# end of autochange + SET(VERSION_TWEAK 10001) SET(VERSION_FLAVOUR altinitytest) """ @@ -27,7 +44,7 @@ def use_version_file(monkeypatch, tmp_path: Path, content: str) -> Path: def test_flavoured_current_version_uses_pinned_file(monkeypatch, tmp_path): - use_version_file(monkeypatch, tmp_path, FLAVOURED_VERSION_FILE) + use_version_file(monkeypatch, tmp_path, UPSTREAM_BLOCK_VERSION_FILE) monkeypatch.setattr( clickhouse_version, "_tweak_from_git", @@ -51,7 +68,7 @@ def test_flavoured_current_version_uses_pinned_file(monkeypatch, tmp_path): def test_flavoured_release_version_round_trips_through_dict(monkeypatch, tmp_path): - use_version_file(monkeypatch, tmp_path, FLAVOURED_VERSION_FILE) + use_version_file(monkeypatch, tmp_path, UPSTREAM_BLOCK_VERSION_FILE) version = CHVersion.get_release_version() restored = CHVersion.from_dict(version.to_dict()) @@ -61,7 +78,7 @@ def test_flavoured_release_version_round_trips_through_dict(monkeypatch, tmp_pat def test_flavoured_version_write_preserves_altinity_fields(monkeypatch, tmp_path): - path = use_version_file(monkeypatch, tmp_path, FLAVOURED_VERSION_FILE) + path = use_version_file(monkeypatch, tmp_path, UPSTREAM_BLOCK_VERSION_FILE) CHVersion.get_release_version().write() @@ -72,33 +89,6 @@ def test_flavoured_version_write_preserves_altinity_fields(monkeypatch, tmp_path assert "SET(VERSION_DESCRIBE v26.8.1.10001.altinitytest)" in content -def test_unflavoured_current_version_keeps_git_derived_tweak(monkeypatch, tmp_path): - use_version_file( - monkeypatch, - tmp_path, - """\ -SET(VERSION_REVISION 54513) -SET(VERSION_MAJOR 26) -SET(VERSION_MINOR 8) -SET(VERSION_PATCH 1) -SET(VERSION_GITHASH release-sha) -SET(VERSION_DESCRIBE v26.8.1.1-lts) -SET(VERSION_STRING 26.8.1.1) -""", - ) - monkeypatch.setattr(clickhouse_version, "_tweak_from_git", lambda _: 42) - monkeypatch.setattr( - clickhouse_version.Shell, "get_output", lambda *_args, **_kwargs: "head-sha" - ) - - version = CHVersion.get_current_version() - - assert version.string == "26.8.1.42" - assert version.describe == "v26.8.1.42-lts" - assert version.flavour == "" - assert version.githash == "head-sha" - - class FakeInfo: pr_number = 123 pr_url = "https://example.test/pull/123" @@ -128,61 +118,6 @@ def test_pr_build_preserves_flavoured_tweak(monkeypatch): ) -def test_pr_build_still_pins_unflavoured_tweak(monkeypatch): - version = CHVersion( - 26, - 8, - 1, - 54513, - tweak=42, - githash="file-sha", - version_type="lts", - ) - monkeypatch.setattr( - CHVersion, "get_current_version", classmethod(lambda cls, **kwargs: version) - ) - - assert version_log._build_version(FakeInfo()).string == "26.8.1.1" - - -def test_version_log_mirrors_version_into_workflow_custom_data(monkeypatch): - stored = {} - config = type("Config", (), {"custom_data": {}, "dump": lambda self: self})() - - class FakeRunConfig: - @classmethod - def from_fs(cls, workflow_name): - assert workflow_name == "PullRequest" - return config - - class FakeCIDB: - def insert_json(self, table, json_str): - stored["cidb"] = (table, json_str) - - class FakeVersion: - string = "26.8.1.10001.altinitytest" - - def to_dict(self): - return {"string": self.string, "flavour": "altinitytest", "tweak": 10001} - - def store_version_data_in_ci_pipeline(self): - stored["kv"] = self.to_dict() - - monkeypatch.setattr(version_log, "Info", lambda: FakeInfo()) - monkeypatch.setattr(version_log, "RunConfig", FakeRunConfig, raising=False) - monkeypatch.setattr(version_log, "CIDBCluster", FakeCIDB) - monkeypatch.setattr(version_log, "_build_version", lambda _info: FakeVersion()) - monkeypatch.setattr(version_log.Shell, "check", lambda *_args, **_kwargs: True) - monkeypatch.setattr( - version_log.Shell, "get_output", lambda *_args, **_kwargs: "parent-sha" - ) - - version_log._add_build_to_version_history() - - assert stored["kv"] == config.custom_data["version"] - assert stored["cidb"][0] == "version_history" - - def test_source_upload_prefers_pipeline_version(monkeypatch): pipeline_version = CHVersion( 26, @@ -211,7 +146,51 @@ def test_source_upload_prefers_pipeline_version(monkeypatch): assert source_upload.get_version().string == "26.8.1.1" -def test_source_upload_restores_submodule_cache(): - from ci.defs.altinity_jobs import AltinityJobConfigs +def test_tag_build_takes_flavour_from_tag(monkeypatch, tmp_path): + use_version_file(monkeypatch, tmp_path, UPSTREAM_BLOCK_VERSION_FILE) + monkeypatch.setenv("GITHUB_REF_TYPE", "tag") + monkeypatch.setenv("GITHUB_REF_NAME", "v26.8.1.10001.altinitystable") + + info = FakeInfo() + info.pr_number = 0 + version = version_log._build_version(info) + + assert version.string == "26.8.1.10001.altinitystable" + assert version.describe == "v26.8.1.10001.altinitystable" + assert version.to_dict()["flavour"] == "altinitystable" + + +def _tag_version_file(tmp_path: Path, tweak: int = 10001) -> Path: + path = tmp_path / "autogenerated_versions.txt" + path.write_text( + UPSTREAM_BLOCK_VERSION_FILE.replace("10001", str(tweak)), encoding="utf-8" + ) + return path + + +@pytest.mark.parametrize( + "tweak,flavour", + [ + (10001, "altinitystable"), + (20001, "altinityantalya"), + (30001, "altinityfips"), + ], +) +def test_check_tag_version_accepts_matching_track(tmp_path, tweak, flavour): + check_tag_version( + f"v26.8.1.{tweak}.{flavour}", _tag_version_file(tmp_path, tweak) + ) + - assert AltinityJobConfigs.source_upload_job.needs_submodules +@pytest.mark.parametrize( + "tag", + [ + "v26.8.1.10001.altinityantalya", + "v26.8.1.10002.altinitystable", + "v26.8.1.10001.altinitytest", + "v26.8.1.1-lts", + ], +) +def test_check_tag_version_rejects_mismatched_tag(tmp_path, tag): + with pytest.raises(ValueError): + check_tag_version(tag, _tag_version_file(tmp_path)) diff --git a/cmake/autogenerated_versions.txt b/cmake/autogenerated_versions.txt index 95803bd73fbb..656c9e262cb8 100644 --- a/cmake/autogenerated_versions.txt +++ b/cmake/autogenerated_versions.txt @@ -7,8 +7,8 @@ SET(VERSION_MAJOR 26) SET(VERSION_MINOR 8) SET(VERSION_PATCH 11) SET(VERSION_GITHASH bedf2ab54b8a0c34afbf2d907eaf933324f43cd6) -SET(VERSION_DESCRIBE v26.8.11.10001.altinitytest) -SET(VERSION_STRING 26.8.11.10001.altinitytest) +SET(VERSION_DESCRIBE v26.8.11.1-lts) +SET(VERSION_STRING 26.8.11.1) # end of autochange SET(VERSION_TWEAK 10001) diff --git a/cmake/version.cmake b/cmake/version.cmake index 9f83cce9ddc8..16ea05b20c9a 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -3,10 +3,12 @@ include(${PROJECT_SOURCE_DIR}/cmake/autogenerated_versions.txt) set(VERSION_EXTRA "" CACHE STRING "") set(VERSION_TWEAK "" CACHE STRING "") -# NOTE(vnemkov): we rely on VERSION_TWEAK portion to be already present in VERSION_STRING -# if (VERSION_TWEAK) -# string(CONCAT VERSION_STRING ${VERSION_STRING} "." ${VERSION_TWEAK}) -# endif () +# NOTE (strtgbb): VERSION_STRING/VERSION_DESCRIBE in autogenerated_versions.txt are left as upstream's +# to avoid merge conflicts; Altinity's VERSION_TWEAK and VERSION_FLAVOUR are set after the autochange block +if (VERSION_FLAVOUR) + set (VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}.${VERSION_FLAVOUR}") + set (VERSION_DESCRIBE "v${VERSION_STRING}") +endif () if (VERSION_EXTRA) string(CONCAT VERSION_STRING ${VERSION_STRING} "." ${VERSION_EXTRA}) diff --git a/tests/ci/version_helper.py b/tests/ci/version_helper.py index e96f4eb25a42..28911afffc0d 100755 --- a/tests/ci/version_helper.py +++ b/tests/ci/version_helper.py @@ -298,14 +298,38 @@ def read_versions(versions_path: Union[Path, str] = FILE_WITH_VERSION_PATH) -> V return versions +ALTINITY_TRACK_BY_FLAVOUR = { + "altinitystable": 1, + "altinityantalya": 2, + "altinityfips": 3, +} + + def check_tag_version( tag: str, versions_path: Union[Path, str] = FILE_WITH_VERSION_PATH ) -> None: - expected_tag = read_versions(versions_path)["describe"] - if tag != expected_tag: + # NOTE (strtgbb): Altinity tags are v....; + # the release flavour comes from the tag, and the tweak's first digit varies by track + versions = read_versions(versions_path) + expected_prefix = ( + f"v{versions['major']}.{versions['minor']}.{versions['patch']}" + f".{versions['tweak']}." + ) + if not tag.startswith(expected_prefix): + raise ValueError( + f"Tag [{tag}] does not start with [{expected_prefix}] from {versions_path}" + ) + flavour = tag[len(expected_prefix) :] + track = ALTINITY_TRACK_BY_FLAVOUR.get(flavour) + if track is None: + raise ValueError( + f"Tag [{tag}] flavour [{flavour}] is not one of " + f"{sorted(ALTINITY_TRACK_BY_FLAVOUR)}" + ) + if versions["tweak"] // 10000 != track: raise ValueError( - f"Tag [{tag}] does not match VERSION_DESCRIBE [{expected_tag}] " - f"in {versions_path}" + f"Tag [{tag}] tweak [{versions['tweak']}] does not match the " + f"{track}xxxx track for flavour [{flavour}]" ) print(f"Tag [{tag}] matches {versions_path}") @@ -541,7 +565,7 @@ def main(): parser.add_argument( "--check-tag", action="store_true", - help="check that GITHUB_REF_NAME matches VERSION_DESCRIBE and exit", + help="check that GITHUB_REF_NAME matches the version parts and exit", ) args = parser.parse_args() From b626928a1aac32d72ad9652b396bb9de87b98d06 Mon Sep 17 00:00:00 2001 From: strtgbb <146047128+strtgbb@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:29:47 -0400 Subject: [PATCH 2/4] Test the tag-check rules against a versions dict, not the version file. `check_tag_against_versions` holds the flavour and track-prefix rules. CI Tests can call it without `get_abs_path`, which needs a `.git` directory that the Docker pytest tree does not have. https://github.com/Altinity/ClickHouse/actions/runs/37366277057/job/111984727269 https://github.com/Altinity/ClickHouse/pull/2485 Co-authored-by: Cursor --- ci/tests/test_clickhouse_version.py | 20 +++++++------------- tests/ci/version_helper.py | 14 ++++++++------ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/ci/tests/test_clickhouse_version.py b/ci/tests/test_clickhouse_version.py index 08717910cbe5..419f1130f959 100644 --- a/ci/tests/test_clickhouse_version.py +++ b/ci/tests/test_clickhouse_version.py @@ -15,7 +15,7 @@ ) sys.path.insert(0, _CI_DIR) try: - from version_helper import check_tag_version # type: ignore[import-not-found] + from version_helper import check_tag_against_versions # type: ignore[import-not-found] finally: sys.path.remove(_CI_DIR) @@ -160,12 +160,8 @@ def test_tag_build_takes_flavour_from_tag(monkeypatch, tmp_path): assert version.to_dict()["flavour"] == "altinitystable" -def _tag_version_file(tmp_path: Path, tweak: int = 10001) -> Path: - path = tmp_path / "autogenerated_versions.txt" - path.write_text( - UPSTREAM_BLOCK_VERSION_FILE.replace("10001", str(tweak)), encoding="utf-8" - ) - return path +def _tag_versions(tweak: int = 10001) -> dict: + return {"major": 26, "minor": 8, "patch": 1, "tweak": tweak} @pytest.mark.parametrize( @@ -176,10 +172,8 @@ def _tag_version_file(tmp_path: Path, tweak: int = 10001) -> Path: (30001, "altinityfips"), ], ) -def test_check_tag_version_accepts_matching_track(tmp_path, tweak, flavour): - check_tag_version( - f"v26.8.1.{tweak}.{flavour}", _tag_version_file(tmp_path, tweak) - ) +def test_check_tag_version_accepts_matching_track(tweak, flavour): + check_tag_against_versions(f"v26.8.1.{tweak}.{flavour}", _tag_versions(tweak)) @pytest.mark.parametrize( @@ -191,6 +185,6 @@ def test_check_tag_version_accepts_matching_track(tmp_path, tweak, flavour): "v26.8.1.1-lts", ], ) -def test_check_tag_version_rejects_mismatched_tag(tmp_path, tag): +def test_check_tag_version_rejects_mismatched_tag(tag): with pytest.raises(ValueError): - check_tag_version(tag, _tag_version_file(tmp_path)) + check_tag_against_versions(tag, _tag_versions()) diff --git a/tests/ci/version_helper.py b/tests/ci/version_helper.py index 28911afffc0d..ab954a9e6ccd 100755 --- a/tests/ci/version_helper.py +++ b/tests/ci/version_helper.py @@ -305,19 +305,16 @@ def read_versions(versions_path: Union[Path, str] = FILE_WITH_VERSION_PATH) -> V } -def check_tag_version( - tag: str, versions_path: Union[Path, str] = FILE_WITH_VERSION_PATH -) -> None: +def check_tag_against_versions(tag: str, versions: VERSIONS) -> None: # NOTE (strtgbb): Altinity tags are v....; # the release flavour comes from the tag, and the tweak's first digit varies by track - versions = read_versions(versions_path) expected_prefix = ( f"v{versions['major']}.{versions['minor']}.{versions['patch']}" f".{versions['tweak']}." ) if not tag.startswith(expected_prefix): raise ValueError( - f"Tag [{tag}] does not start with [{expected_prefix}] from {versions_path}" + f"Tag [{tag}] does not start with [{expected_prefix}]" ) flavour = tag[len(expected_prefix) :] track = ALTINITY_TRACK_BY_FLAVOUR.get(flavour) @@ -326,12 +323,17 @@ def check_tag_version( f"Tag [{tag}] flavour [{flavour}] is not one of " f"{sorted(ALTINITY_TRACK_BY_FLAVOUR)}" ) - if versions["tweak"] // 10000 != track: + if int(versions["tweak"]) // 10000 != track: raise ValueError( f"Tag [{tag}] tweak [{versions['tweak']}] does not match the " f"{track}xxxx track for flavour [{flavour}]" ) + +def check_tag_version( + tag: str, versions_path: Union[Path, str] = FILE_WITH_VERSION_PATH +) -> None: + check_tag_against_versions(tag, read_versions(versions_path)) print(f"Tag [{tag}] matches {versions_path}") From 24ec50d01b3175c021b68e1335fcd030af79645e Mon Sep 17 00:00:00 2001 From: strtgbb <146047128+strtgbb@users.noreply.github.com> Date: Thu, 8 Oct 2026 12:05:33 -0400 Subject: [PATCH 3/4] support a pre-release flavour for all tracks. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `--check-tag` now requires the tag flavour to be the release name for `VERSION_FLAVOUR` (`altinitytest` → `altinitystable`, `antalyatest` → `altinityantalya`, `altinityfipstest` → `altinityfips`). Co-authored-by: Cursor --- ci/tests/test_clickhouse_version.py | 66 ++++++++++++++++++++--------- cmake/autogenerated_versions.txt | 9 ++++ tests/ci/version_helper.py | 23 ++++++++-- 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/ci/tests/test_clickhouse_version.py b/ci/tests/test_clickhouse_version.py index 419f1130f959..ec1db4d8725b 100644 --- a/ci/tests/test_clickhouse_version.py +++ b/ci/tests/test_clickhouse_version.py @@ -146,45 +146,69 @@ def test_source_upload_prefers_pipeline_version(monkeypatch): assert source_upload.get_version().string == "26.8.1.1" -def test_tag_build_takes_flavour_from_tag(monkeypatch, tmp_path): - use_version_file(monkeypatch, tmp_path, UPSTREAM_BLOCK_VERSION_FILE) +@pytest.mark.parametrize( + "tweak,prerelease,release", + [ + (10001, "altinitytest", "altinitystable"), + (20001, "antalyatest", "altinityantalya"), + (30001, "altinityfipstest", "altinityfips"), + ], +) +def test_tag_build_takes_flavour_from_tag( + monkeypatch, tmp_path, tweak, prerelease, release +): + content = UPSTREAM_BLOCK_VERSION_FILE.replace("10001", str(tweak)).replace( + "altinitytest", prerelease + ) + use_version_file(monkeypatch, tmp_path, content) monkeypatch.setenv("GITHUB_REF_TYPE", "tag") - monkeypatch.setenv("GITHUB_REF_NAME", "v26.8.1.10001.altinitystable") + monkeypatch.setenv("GITHUB_REF_NAME", f"v26.8.1.{tweak}.{release}") info = FakeInfo() info.pr_number = 0 version = version_log._build_version(info) - assert version.string == "26.8.1.10001.altinitystable" - assert version.describe == "v26.8.1.10001.altinitystable" - assert version.to_dict()["flavour"] == "altinitystable" + assert version.string == f"26.8.1.{tweak}.{release}" + assert version.describe == f"v26.8.1.{tweak}.{release}" + assert version.to_dict()["flavour"] == release -def _tag_versions(tweak: int = 10001) -> dict: - return {"major": 26, "minor": 8, "patch": 1, "tweak": tweak} +def _tag_versions(tweak: int = 10001, flavour: str = "altinitytest") -> dict: + return { + "major": 26, + "minor": 8, + "patch": 1, + "tweak": tweak, + "flavour": flavour, + } @pytest.mark.parametrize( - "tweak,flavour", + "tweak,prerelease,release", [ - (10001, "altinitystable"), - (20001, "altinityantalya"), - (30001, "altinityfips"), + (10001, "altinitytest", "altinitystable"), + (20001, "antalyatest", "altinityantalya"), + (30001, "altinityfipstest", "altinityfips"), + (20001, "altinityantalya", "altinityantalya"), ], ) -def test_check_tag_version_accepts_matching_track(tweak, flavour): - check_tag_against_versions(f"v26.8.1.{tweak}.{flavour}", _tag_versions(tweak)) +def test_check_tag_version_accepts_matching_track(tweak, prerelease, release): + check_tag_against_versions( + f"v26.8.1.{tweak}.{release}", _tag_versions(tweak, prerelease) + ) @pytest.mark.parametrize( - "tag", + "tweak,flavour,tag", [ - "v26.8.1.10001.altinityantalya", - "v26.8.1.10002.altinitystable", - "v26.8.1.10001.altinitytest", - "v26.8.1.1-lts", + (10001, "altinitytest", "v26.8.1.10001.altinityantalya"), + (10001, "altinitytest", "v26.8.1.10002.altinitystable"), + (10001, "altinitytest", "v26.8.1.10001.altinitytest"), + (10001, "altinitytest", "v26.8.1.1-lts"), + (20001, "altinitytest", "v26.8.1.20001.altinityantalya"), + (20001, "antalyatest", "v26.8.1.20001.antalyatest"), ], ) -def test_check_tag_version_rejects_mismatched_tag(tag): +def test_check_tag_version_rejects_mismatched_tag(tweak, flavour, tag): with pytest.raises(ValueError): - check_tag_against_versions(tag, _tag_versions()) + check_tag_against_versions(tag, _tag_versions(tweak, flavour)) diff --git a/cmake/autogenerated_versions.txt b/cmake/autogenerated_versions.txt index 656c9e262cb8..b10502897fd7 100644 --- a/cmake/autogenerated_versions.txt +++ b/cmake/autogenerated_versions.txt @@ -11,6 +11,15 @@ SET(VERSION_DESCRIBE v26.8.11.1-lts) SET(VERSION_STRING 26.8.11.1) # end of autochange +# Note (strtgbb): +# Do not customize VERSION_DESCRIBE or VERSION_STRING +# They will be overwritten at compile time +# Only modify the below + SET(VERSION_TWEAK 10001) SET(VERSION_FLAVOUR altinitytest) +# Pre-release flavour (this file) → release flavour (tag): +# altinitytest → altinitystable +# antalyatest → altinityantalya +# altinityfipstest → altinityfips diff --git a/tests/ci/version_helper.py b/tests/ci/version_helper.py index ab954a9e6ccd..9afc3153ef48 100755 --- a/tests/ci/version_helper.py +++ b/tests/ci/version_helper.py @@ -298,6 +298,13 @@ def read_versions(versions_path: Union[Path, str] = FILE_WITH_VERSION_PATH) -> V return versions +# Pre-release flavour (VERSION_FLAVOUR) → release flavour (tag) +ALTINITY_RELEASE_BY_PRERELEASE = { + "altinitytest": "altinitystable", + "antalyatest": "altinityantalya", + "altinityfipstest": "altinityfips", +} + ALTINITY_TRACK_BY_FLAVOUR = { "altinitystable": 1, "altinityantalya": 2, @@ -316,18 +323,26 @@ def check_tag_against_versions(tag: str, versions: VERSIONS) -> None: raise ValueError( f"Tag [{tag}] does not start with [{expected_prefix}]" ) - flavour = tag[len(expected_prefix) :] - track = ALTINITY_TRACK_BY_FLAVOUR.get(flavour) + tag_flavour = tag[len(expected_prefix) :] + track = ALTINITY_TRACK_BY_FLAVOUR.get(tag_flavour) if track is None: raise ValueError( - f"Tag [{tag}] flavour [{flavour}] is not one of " + f"Tag [{tag}] flavour [{tag_flavour}] is not one of " f"{sorted(ALTINITY_TRACK_BY_FLAVOUR)}" ) if int(versions["tweak"]) // 10000 != track: raise ValueError( f"Tag [{tag}] tweak [{versions['tweak']}] does not match the " - f"{track}xxxx track for flavour [{flavour}]" + f"{track}xxxx track for flavour [{tag_flavour}]" ) + file_flavour = str(versions.get("flavour") or "") + if file_flavour: + expected_release = ALTINITY_RELEASE_BY_PRERELEASE.get(file_flavour, file_flavour) + if tag_flavour != expected_release: + raise ValueError( + f"Tag [{tag}] flavour [{tag_flavour}] does not match " + f"VERSION_FLAVOUR [{file_flavour}] (expected [{expected_release}])" + ) def check_tag_version( From 68f16d51fef040b1e23bc539102cf90bf5e6d603 Mon Sep 17 00:00:00 2001 From: strtgbb <146047128+strtgbb@users.noreply.github.com> Date: Thu, 8 Oct 2026 13:48:06 -0400 Subject: [PATCH 4/4] Derive flavour and tweak track from the branch so the version file no longer needs per-track edits. Co-authored-by: Cursor --- ci/jobs/scripts/workflow_hooks/version_log.py | 56 ++++++++++++++-- ci/tests/test_clickhouse_version.py | 65 ++++++++++++++++--- tests/ci/version_helper.py | 34 +++++----- 3 files changed, 127 insertions(+), 28 deletions(-) diff --git a/ci/jobs/scripts/workflow_hooks/version_log.py b/ci/jobs/scripts/workflow_hooks/version_log.py index e7af07cdcea0..c590b42ca009 100644 --- a/ci/jobs/scripts/workflow_hooks/version_log.py +++ b/ci/jobs/scripts/workflow_hooks/version_log.py @@ -9,6 +9,39 @@ from ci.jobs.scripts.clickhouse_version import CHVersion +# NOTE (strtgbb): release flavour → tweak track. Pre-release flavours pair as +# altinitytest/altinitystable, antalyatest/altinityantalya, altinityfipstest/altinityfips. +_TRACK_BY_RELEASE_FLAVOUR = { + "altinitystable": 1, + "altinityantalya": 2, + "altinityfips": 3, +} + + +def _track_from_branch(branch): + """(track, pre-release flavour) from a release-branch name, or None. + + PRs use the base branch; pushes use the branch. `fips-` / `antalya-` / + `stable-` prefixes select the track (`fips-` first). Anything else keeps + the file values.""" + if not branch: + return None + if branch.startswith("fips-"): + return 3, "altinityfipstest" + if branch.startswith("antalya-"): + return 2, "antalyatest" + if branch.startswith("stable-"): + return 1, "altinitytest" + return None + + +def _apply_track(version, track, flavour): + version.flavour = flavour + return version.with_tweak(track * 10000 + version.tweak % 10000).with_description( + flavour + ) + + def _build_version(info): """The build version recorded for this run. @@ -17,14 +50,27 @@ def _build_version(info): close/reopen and re-runs of the same PR as `master` advances. Artifacts are keyed by the head SHA, so an unpinned tweak would store diverging version strings under one artifact prefix. Flavoured Altinity versions keep the - explicit tweak committed in the version file.""" + explicit tweak committed in the version file. When the branch identifies a + track, CI replaces the flavour and the tweak's leading digit; the counter + (`tweak % 10000`) stays as committed.""" version = CHVersion.get_current_version(no_strict=True) if info.pr_number != 0 and not version.flavour: version = version.with_tweak(1) - # NOTE (strtgbb): get the release flavour from the tag, validated with the --check-tag pre-hook - if version.flavour and os.getenv("GITHUB_REF_TYPE") == "tag": - version.flavour = os.getenv("GITHUB_REF_NAME", "").rsplit(".", 1)[-1] - version = version.with_description(version.flavour) + if not version.flavour: + return version + # NOTE (strtgbb): tag flavour and track come from the tag; --check-tag + # validates the counter. Otherwise the branch overrides a stale file. + if os.getenv("GITHUB_REF_TYPE") == "tag": + flavour = os.getenv("GITHUB_REF_NAME", "").rsplit(".", 1)[-1] + track = _TRACK_BY_RELEASE_FLAVOUR.get(flavour) + if track is None: + version.flavour = flavour + return version.with_description(flavour) + return _apply_track(version, track, flavour) + branch = info.base_branch if info.pr_number != 0 else info.git_branch + derived = _track_from_branch(branch) + if derived: + version = _apply_track(version, *derived) return version diff --git a/ci/tests/test_clickhouse_version.py b/ci/tests/test_clickhouse_version.py index ec1db4d8725b..c1716c35f71f 100644 --- a/ci/tests/test_clickhouse_version.py +++ b/ci/tests/test_clickhouse_version.py @@ -95,6 +95,7 @@ class FakeInfo: sha = "pr-sha" commit_url = "https://example.test/commit/pr-sha" git_branch = "feature" + base_branch = "" workflow_name = "PullRequest" @@ -173,6 +174,51 @@ def test_tag_build_takes_flavour_from_tag( assert version.to_dict()["flavour"] == release +def test_tag_build_applies_tag_track_when_file_is_stale(monkeypatch, tmp_path): + use_version_file(monkeypatch, tmp_path, UPSTREAM_BLOCK_VERSION_FILE) + monkeypatch.setenv("GITHUB_REF_TYPE", "tag") + monkeypatch.setenv("GITHUB_REF_NAME", "v26.8.1.20001.altinityantalya") + + info = FakeInfo() + info.pr_number = 0 + version = version_log._build_version(info) + + assert version.string == "26.8.1.20001.altinityantalya" + assert version.describe == "v26.8.1.20001.altinityantalya" + assert version.to_dict()["flavour"] == "altinityantalya" + + +@pytest.mark.parametrize( + "pr_number,git_branch,base_branch,expected", + [ + (123, "fix/antalya-26.8/foo", "stable-26.8", "26.8.1.10001.altinitytest"), + (123, "fix/stable-26.8/foo", "antalya-26.8", "26.8.1.20001.antalyatest"), + (123, "fix/foo", "fips-25.3", "26.8.1.30001.altinityfipstest"), + (0, "stable-26.8", "", "26.8.1.10001.altinitytest"), + (0, "antalya-26.8", "", "26.8.1.20001.antalyatest"), + (0, "fips-25.3", "", "26.8.1.30001.altinityfipstest"), + (0, "fix/antalya-26.8/foo", "", "26.8.1.10001.altinitytest"), + (0, "feature", "", "26.8.1.10001.altinitytest"), + (123, "fix/stable-26.8/foo", "master", "26.8.1.10001.altinitytest"), + ], +) +def test_branch_overrides_flavour_and_track( + monkeypatch, tmp_path, pr_number, git_branch, base_branch, expected +): + use_version_file(monkeypatch, tmp_path, UPSTREAM_BLOCK_VERSION_FILE) + monkeypatch.delenv("GITHUB_REF_TYPE", raising=False) + + info = FakeInfo() + info.pr_number = pr_number + info.git_branch = git_branch + info.base_branch = base_branch + version = version_log._build_version(info) + + assert version.string == expected + assert version.describe == f"v{expected}" + assert version.to_dict()["flavour"] == expected.rsplit(".", 1)[-1] + + def _tag_versions(tweak: int = 10001, flavour: str = "altinitytest") -> dict: return { "major": 26, @@ -184,17 +230,21 @@ def _tag_versions(tweak: int = 10001, flavour: str = "altinitytest") -> dict: @pytest.mark.parametrize( - "tweak,prerelease,release", + "file_tweak,prerelease,tag_tweak,release", [ - (10001, "altinitytest", "altinitystable"), - (20001, "antalyatest", "altinityantalya"), - (30001, "altinityfipstest", "altinityfips"), - (20001, "altinityantalya", "altinityantalya"), + (10001, "altinitytest", 10001, "altinitystable"), + (20001, "antalyatest", 20001, "altinityantalya"), + (30001, "altinityfipstest", 30001, "altinityfips"), + (20001, "altinityantalya", 20001, "altinityantalya"), + (10001, "altinitytest", 20001, "altinityantalya"), + (20001, "antalyatest", 10001, "altinitystable"), ], ) -def test_check_tag_version_accepts_matching_track(tweak, prerelease, release): +def test_check_tag_version_accepts_matching_counter( + file_tweak, prerelease, tag_tweak, release +): check_tag_against_versions( - f"v26.8.1.{tweak}.{release}", _tag_versions(tweak, prerelease) + f"v26.8.1.{tag_tweak}.{release}", _tag_versions(file_tweak, prerelease) ) @@ -205,7 +255,6 @@ def test_check_tag_version_accepts_matching_track(tweak, prerelease, release): (10001, "altinitytest", "v26.8.1.10002.altinitystable"), (10001, "altinitytest", "v26.8.1.10001.altinitytest"), (10001, "altinitytest", "v26.8.1.1-lts"), - (20001, "altinitytest", "v26.8.1.20001.altinityantalya"), (20001, "antalyatest", "v26.8.1.20001.antalyatest"), ], ) diff --git a/tests/ci/version_helper.py b/tests/ci/version_helper.py index 9afc3153ef48..320013f7af31 100755 --- a/tests/ci/version_helper.py +++ b/tests/ci/version_helper.py @@ -313,36 +313,40 @@ def read_versions(versions_path: Union[Path, str] = FILE_WITH_VERSION_PATH) -> V def check_tag_against_versions(tag: str, versions: VERSIONS) -> None: - # NOTE (strtgbb): Altinity tags are v....; - # the release flavour comes from the tag, and the tweak's first digit varies by track + # NOTE (strtgbb): Altinity tags are v..... + # Flavour and the tweak's track digit come from the tag; the file supplies + # major/minor/patch and the release counter (tweak % 10000) only. expected_prefix = ( - f"v{versions['major']}.{versions['minor']}.{versions['patch']}" - f".{versions['tweak']}." + f"v{versions['major']}.{versions['minor']}.{versions['patch']}." ) if not tag.startswith(expected_prefix): raise ValueError( f"Tag [{tag}] does not start with [{expected_prefix}]" ) - tag_flavour = tag[len(expected_prefix) :] + try: + tag_tweak_str, tag_flavour = tag[len(expected_prefix) :].split(".", 1) + tag_tweak = int(tag_tweak_str) + except ValueError: + raise ValueError( + f"Tag [{tag}] is not v...." + ) track = ALTINITY_TRACK_BY_FLAVOUR.get(tag_flavour) if track is None: raise ValueError( f"Tag [{tag}] flavour [{tag_flavour}] is not one of " f"{sorted(ALTINITY_TRACK_BY_FLAVOUR)}" ) - if int(versions["tweak"]) // 10000 != track: + if tag_tweak // 10000 != track: raise ValueError( - f"Tag [{tag}] tweak [{versions['tweak']}] does not match the " + f"Tag [{tag}] tweak [{tag_tweak}] does not match the " f"{track}xxxx track for flavour [{tag_flavour}]" ) - file_flavour = str(versions.get("flavour") or "") - if file_flavour: - expected_release = ALTINITY_RELEASE_BY_PRERELEASE.get(file_flavour, file_flavour) - if tag_flavour != expected_release: - raise ValueError( - f"Tag [{tag}] flavour [{tag_flavour}] does not match " - f"VERSION_FLAVOUR [{file_flavour}] (expected [{expected_release}])" - ) + file_tweak = int(versions["tweak"]) + if tag_tweak % 10000 != file_tweak % 10000: + raise ValueError( + f"Tag [{tag}] counter [{tag_tweak % 10000}] does not match " + f"VERSION_TWEAK [{file_tweak}] counter [{file_tweak % 10000}]" + ) def check_tag_version(