Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions .github/scripts/commit_prefix_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@
# Regex patterns
PREFIX_RE = re.compile(r"^[a-z0-9_]+:", re.IGNORECASE)
SIGNED_OFF_RE = re.compile(r"Signed-off-by:", re.IGNORECASE)
FENCED_BLOCK_RE = re.compile(
r"""
(```|~~~) # fence start
[^\n]*\n # optional language
.*?
\1 # matching fence end
""",
re.DOTALL | re.VERBOSE,
)

def strip_fenced_code_blocks(text: str) -> str:
"""
Remove fenced code blocks (``` or ~~~) from commit message body.
"""
return FENCED_BLOCK_RE.sub("", text)


# ------------------------------------------------
Expand Down Expand Up @@ -109,6 +124,8 @@ def detect_bad_squash(body):
- Multiple Signed-off-by lines in body → BAD (ONLY for this function)
"""

body = strip_fenced_code_blocks(body)

# Normalize and discard empty lines
lines = [l.strip() for l in body.splitlines() if l.strip()]

Expand Down Expand Up @@ -138,6 +155,8 @@ def validate_commit(commit):
first_line, *rest = msg.split("\n")
body = "\n".join(rest)

body = strip_fenced_code_blocks(body)

# Subject must start with a prefix
subject_prefix_match = PREFIX_RE.match(first_line)
if not subject_prefix_match:
Expand Down Expand Up @@ -216,16 +235,25 @@ def validate_commit(commit):
# prefix, check if the subject prefix is in the expected list. If it is, allow it
# (because the corresponding file exists). Only reject if it's not in the expected list
# or if it's an umbrella prefix that doesn't match.
if len(non_build_prefixes) > 1 and subj_lower not in umbrella_prefixes:
# If subject prefix is in expected list, it's valid (the corresponding file exists)
if subj_lower not in expected_lower:
if len(non_build_prefixes) > 1:
# Only umbrella prefixes are allowed to cover multiple components
if subj_lower in umbrella_prefixes:
# Ensure all changed paths are within the umbrella domain
norm_paths = [p.replace(os.sep, "/") for p in files]
if not all(p.startswith("lib/") for p in norm_paths):
expected_list = sorted(expected)
expected_str = ", ".join(expected_list)
return False, (
f"Subject prefix '{subject_prefix}' does not match files changed.\n"
f"Expected one of: {expected_str}"
)
elif subj_lower not in umbrella_prefixes:
expected_list = sorted(expected)
expected_str = ", ".join(expected_list)
return False, (
f"Subject prefix '{subject_prefix}' does not match files changed.\n"
f"Expected one of: {expected_str}"
)
# Subject prefix is in expected list, so it's valid - no need to check further

# Subject prefix must be one of the expected ones
if subj_lower not in expected_lower:
Expand Down
25 changes: 25 additions & 0 deletions .github/scripts/tests/test_commit_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,31 @@ def test_valid_commit_bin_prefix_for_fluent_bit():
assert ok is True


def test_valid_commit_with_fenced_code_block_in_body():
"""
Commits containing fenced code blocks in the body should NOT fail validation.

Code blocks often include YAML, shell output, or logs that may look like
subject prefixes or configuration directives. These must be ignored by
the linter to avoid false positives.
"""
commit = make_commit(
"out_s3: validate config earlier\n\n"
"This commit improves validation.\n\n"
"```yaml\n"
"pipeline:\n"
" inputs:\n"
" - name: dummy\n"
" tag: test\n"
"```\n\n"
"Signed-off-by: User",
["plugins/out_s3/s3.c"]
)

ok, _ = validate_commit(commit)
assert ok is True


# -----------------------------------------------------------
# Tests: validate_commit ERROR CASES
# -----------------------------------------------------------
Expand Down
Loading