diff --git a/.github/scripts/commit_prefix_check.py b/.github/scripts/commit_prefix_check.py index 01dc23ccf12..8558639f9df 100644 --- a/.github/scripts/commit_prefix_check.py +++ b/.github/scripts/commit_prefix_check.py @@ -178,6 +178,47 @@ def detect_bad_squash(body): # Validate commit based on expected behavior and test rules # ------------------------------------------------ def validate_commit(commit): + VERSION_PATTERN = re.compile( + r"set\(FLB_VERSION_(MAJOR|MINOR|PATCH)\s+\d+\)" + ) + + def is_version_bump(commit): + if not commit.parents: + return False + + diffs = commit.diff(commit.parents[0], create_patch=True) + found_version_change = False + saw_cmakelists = False + + for d in diffs: + path = (d.b_path or "").replace("\\", "/") + + if not path.endswith("CMakeLists.txt"): + continue + + saw_cmakelists = True + patch = d.diff.decode(errors="ignore") + + for line in patch.splitlines(): + stripped = line.lstrip() + + if not stripped: + continue + + if stripped.startswith(("diff --git ", "index ", "@@ ", "+++ ", "--- ")): + continue + + if not stripped.startswith(("+", "-")): + continue + + if VERSION_PATTERN.search(stripped): + found_version_change = True + continue + + return False + + return saw_cmakelists and found_version_change + msg = commit.message.strip() first_line, *rest = msg.split("\n") body = "\n".join(rest) @@ -225,6 +266,14 @@ def validate_commit(commit): "Use a full-depth checkout for commit-prefix validation." ) + # ------------------------------- + # release special rule + # ------------------------------- + if is_version_bump(commit): + if not first_line.startswith("release:"): + return False, "Version bump must use release: prefix" + return True, "" + expected, build_optional = infer_prefix_from_paths(files) # When no prefix can be inferred (docs/tools), allow anything diff --git a/.github/scripts/tests/test_commit_lint.py b/.github/scripts/tests/test_commit_lint.py index 51e239d9d99..ff2ef079eb3 100644 --- a/.github/scripts/tests/test_commit_lint.py +++ b/.github/scripts/tests/test_commit_lint.py @@ -910,3 +910,68 @@ def test_valid_commit_multiline_subject_ignored(): ) ok, _ = validate_commit(commit) assert ok is True + + +class FakeDiff: + def __init__(self, path, patch): + self.b_path = path + self.diff = patch.encode() + + +class FakeStats: + def __init__(self, files): + self.files = {f: {} for f in files} + + +class FakeCommit: + def __init__(self, message, diffs): + self.message = message + self._diffs = diffs + self.parents = [object()] + + file_paths = [d.b_path for d in diffs] + self.stats = FakeStats(file_paths) + + def diff(self, parent, create_patch=True): + return self._diffs + + +def make_fake_commit(message, changes): + """ + changes: list of (path, patch) + """ + diffs = [FakeDiff(path, patch) for path, patch in changes] + return FakeCommit(message, diffs) + +def test_release_with_version_bump(): + commit = make_fake_commit( + "release: update to 5.0.2\n\nSigned-off-by: User", + [ + ("CMakeLists.txt", """ +-set(FLB_VERSION_PATCH 0) ++set(FLB_VERSION_PATCH 2) +"""), + ("dockerfiles/Dockerfile", """ +-FROM fluent-bit:5.0.0 ++FROM fluent-bit:5.0.2 +""") + ] + ) + + ok, msg = validate_commit(commit) + assert ok is True, msg + +def test_release_rejected_when_cmakelists_has_non_version_change(): + commit = make_fake_commit( + "release: update to 5.0.2\n\nSigned-off-by: User", + [ + ("CMakeLists.txt", """ +-set(FLB_VERSION_PATCH 0) ++set(FLB_VERSION_PATCH 2) ++set(SOME_FLAG ON) +""") + ] + ) + + ok, _ = validate_commit(commit) + assert ok is False