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
19 changes: 18 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,22 @@ def keep_ai_rules(scope: ScopeResult) -> ScopeResult:
return skip_when_no_ai_rule_ran(scope)


#: Rules about a commit rather than about a line of text: it has a body
#: (CC011), a sign-off (CC012), an author that is no AI tool (CC017). A pull
#: request title has neither body nor trailers nor an author of its own, so
#: judged as a commit message it could never pass the first two, and the
#: third would judge whatever git identity the runner has.
COMMIT_ONLY_RULE_IDS = frozenset({"CC011", "CC012", "CC017"})
Comment thread
shenxianpeng marked this conversation as resolved.
Resolved


def drop_commit_only_rules(scope: ScopeResult) -> ScopeResult:
"""Narrow the PR title scope to the rules a one-line title can meet."""
scope.checks = [
c for c in scope.checks if c.get("rule_id") not in COMMIT_ONLY_RULE_IDS
Comment thread
shenxianpeng marked this conversation as resolved.
Resolved
]
return scope


def get_pr_title() -> str | None:
"""Read PR title from GitHub event payload."""
if not is_pr_event():
Expand Down Expand Up @@ -972,7 +988,8 @@ def run_commit_check() -> tuple[int, list[ScopeResult]]:
if PR_TITLE_ENABLED and is_pr_event():
pr_title = get_pr_title()
if pr_title:
results.append(check_scope("PR title", ["--message"], input_text=pr_title))
scope = check_scope("PR title", ["--message"], input_text=pr_title)
results.append(drop_commit_only_rules(scope))

# ---- 2. PR description ------------------------------------------------
if (PR_BODY_AI_ATTRIBUTION_ENABLED or ai_label_name()) and is_pr_event():
Expand Down
39 changes: 39 additions & 0 deletions main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,33 @@ def test_pr_title_check_runs_when_enabled(self):
"PR title", ["--message"], input_text="feat: a feature"
)

def test_pr_title_is_not_held_to_commit_only_rules(self):
# require_body and require_signed_off_by judge a commit; a title has
# no body or trailers, so it could never pass them.
title = main.ScopeResult(
label="PR title",
checks=[
make_check("message", "pass", "CC001", value="feat: add login"),
make_check("require_body", "fail", "CC011", value="feat: add login"),
make_check("require_signed_off_by", "fail", "CC012"),
make_check("ai_author", "fail", "CC017", value="Claude <x@y.z>"),
],
)
with (
patch("main.PR_TITLE_ENABLED", True),
patch("main.MESSAGE_ENABLED", False),
patch("main.BRANCH_ENABLED", False),
patch("main.AUTHOR_NAME_ENABLED", False),
patch("main.AUTHOR_EMAIL_ENABLED", False),
patch("main.is_pr_event", return_value=True),
patch("main.get_pr_title", return_value="feat: add login"),
patch("main.check_scope", return_value=title),
patch("main.run_other_checks", return_value=[]),
):
rc, results = main.run_commit_check()
self.assertEqual(rc, 0)
self.assertEqual([c["rule_id"] for c in results[0].checks], ["CC001"])

def test_pr_title_failure_propagates(self):
with (
patch("main.PR_TITLE_ENABLED", True),
Expand Down Expand Up @@ -4222,6 +4249,18 @@ def test_failing_message(self):
main.render_report([scope]),
)

def test_a_title_meets_only_the_rules_a_title_can(self):
with open(self.config, "w", encoding="utf-8") as f:
f.write("[commit]\nrequire_body = true\nrequire_signed_off_by = true\n")
rc, data = self._run("feat: add login page")
failed = {c["rule_id"] for c in data["checks"] if c["status"] == "fail"}
self.assertEqual(rc, 1)
self.assertEqual(failed, {"CC011", "CC012"})
scope = main.drop_commit_only_rules(
main.ScopeResult(label="PR title", checks=data["checks"])
)
self.assertEqual(scope.status, "pass")


class TestStderrIsNotPartOfTheJson(unittest.TestCase):
"""stdout is the JSON; stderr is what the CLI has to say to a person.
Expand Down
Loading