Tags: commit-check/commit-check
Tags
fix: keep a piped message out of the branch, author, tag and push che… …cks (#579) * fix: keep a piped message out of the branch, author, tag and push checks `printf 'feat: ...' | commit-check --message --branch` failed CC201 with the message as the branch name. Every requested check read the one piped value as its own: the author checks judged the message as a name and an email, --tag as tag names, and --no-force-push parsed message lines as push refs and passed having compared nothing. Next to --message, stdin is now the message and nothing else. The other checks read git, as they already do when the message comes from a file. Without --message nothing changes: a lone --branch, --tag or --no-force-push still reads what is piped to it. * refactor: trim the piped-message fix to a context property Same behaviour, half the diff. The helper that decided whether a check may read stdin as its own value is now ValidationContext.piped_value, the long comments are down to one line each, and main.py no longer keeps a second variable for the value it hands on. The tests keep what pins the fix -- next to -m, branch, author, tag and push checks read git; a bad branch still fails; the push check compares with the upstream; a lone check still reads what is piped -- and drop the engine test that repeated the CLI case and the one that restated the reported run. Against main's code the six combination cases fail and the five lone-check cases pass.
docs: switch the commit-check badge to the new brand (#584) The badge keeps its Signal Blue #2c9ccd, takes an Ink label, and carries the Commit Check mark in place of the generic Git logo, matching commit-check.com and the org branding (commit-check/.github#61). It now links to commit-check.com, where a reader who wonders what the badge means finds out.
chore(deps): bump the github-actions group with 2 updates (#542) Bumps the github-actions group with 2 updates: [CodSpeedHQ/action](https://github.com/codspeedhq/action) and [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `CodSpeedHQ/action` from 5.0.1 to 5.0.3 - [Release notes](https://github.com/codspeedhq/action/releases) - [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md) - [Commits](CodSpeedHQ/action@8847237...4296e51) Updates `actions/attest-build-provenance` from 4.1.1 to 4.2.2 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](actions/attest-build-provenance@0f67c3f...4d10147) --- updated-dependencies: - dependency-name: CodSpeedHQ/action dependency-version: 5.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: actions/attest-build-provenance dependency-version: 4.2.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ci: check this pull request with the commit-check in it (#541) * ci: check this pull request with the commit-check in it The workflow ran commit-check-action, which installs a released commit-check -- so every pull request was checked by the version before it. #540 had its own title rejected by the bug it was fixing, and the same thing happened tonight in the .github repository. A self-test that cannot see the change under test is not a self-test, and the action's version does not track the engine's, so a fix needed two releases and a bump before it could be reproduced here. Install from the checkout instead. The cost is job-summary and pr-comments, which are the action's rendering rather than the engine's behaviour and are covered by the action's own repository. Enumerating the commits is not optional. On a pull_request checkout HEAD is the synthetic merge commit, which the engine skips, so a bare 'commit-check --message' reports a pass having read nothing. Verified on a scratch repository holding one plainly bad commit: the bare form exits 0, this workflow exits 1. HEAD^1..HEAD^2 gives the real commits, and a guard fails loudly if HEAD is not a merge so the failure can never be silence. The pull request title reaches the CLI through the environment. Titles are attacker-controlled, and interpolating one into a run: block is a script injection. * ci: install with the flags the other workflows already use SonarCloud's unpinned-install rule failed the quality gate on a bare 'pip install .'. main.yml and publish-package.yml both carry --only-binary :all: for exactly this, added in #479; this step was the one that did not. Verified it still installs a local path: the resulting version reads 2.13.4.post1.dev4, which is the checkout rather than the release. * ci: close stdin so the branch and author checks cannot hang With stdin left open, commit-check waits to read a message even when only --branch and --author-name were asked for. Reproduced locally: the process runs until killed, which in CI is a step that hangs rather than fails. Closing stdin also decides what the author checks look at, so the comment says which: the author of HEAD, matching what the action reports today. * ci: revert the install flags that did not do what I thought I added --only-binary :all: because main.yml and publish-package.yml carry it and #479 mentions SonarCloud. It does not satisfy this rule -- the alert stayed -- and --upgrade pip added a second flagged line, so the change took the count from one to two. Back to one plain line. The remaining alert needs a decision rather than another flag: main.yml has the identical install and passes only because it is not new code, so locking would mean a hash-pinned requirements file or reviving the stale uv.lock. * refactor: move the checks into the nox session that already existed The session was already there and already wrong: it ran a bare 'commit-check --message', which on a pull_request checkout inspects the merge commit, which the engine skips. Leaving it that way while putting a correct copy in YAML would have left two implementations, with the broken one being the one a contributor reaches for locally. So the logic lives in noxfile.py and the workflow is one line. The same command now reproduces a CI failure on a laptop, which was half the reason for moving off the action. The session adapts rather than assuming CI: HEAD^1..HEAD^2 when the checkout is a merge commit, HEAD otherwise, and the title only when PR_TITLE is set. It still refuses to pass silently -- missing HEAD^2 during a pull_request event is an error, not a fallback. Verified locally: enumerates 2 of 2 commits on a merge ref and 0 on a plain one, exits 1 on a bad title having still run the branch and author checks, exits 0 on a good one, and no longer hangs on stdin. * refactor: keep the nox session simple, and CI logic in the workflow Reverts b95acfd, which moved the pull request checks into the nox session. The session is a developer command -- run commit-check on your working copy -- and folding CI's shape into it made the simple thing complicated for no gain. The justification was wrong too. I claimed the same command would reproduce a CI failure locally, then had to branch on whether HEAD^2 exists, because locally there is no merge commit and no PR title. A command that behaves differently in the two places does not reproduce one from the other. It also introduced a bug the shell never had: filtering on .strip() dropped empty messages, which this repository rejects via allow_empty_commits = false, so such a commit would have been reported as 'HEAD is not a merge commit' instead of as the thing it is. The shell keeps them: printf 'a\0\0b\0' through 'read -r -d' yields a, empty, b. noxfile.py is now byte-identical to main. * ci: check the title, and stop checking messages that get discarded main is linear and every subject ends in (#N): this repository squashes, so the commits on a branch never reach it. The title becomes the subject. Checking each commit was protecting history that does not exist, and cost thirty lines of shell to do it. What is left is two commands. The bare 'commit-check --message' still cannot be one of them -- it reads HEAD, the synthetic merge commit, which the engine skips and would pass having read nothing -- so the title goes in through stdin instead, which sidesteps HEAD entirely and needs no enumeration. Contributors lose CI feedback on intermediate commit messages. The pre-commit hook is where that belongs anyway: it arrives while the message is being written rather than a round trip later. Verified on a merge ref: bad title 1, good title 0, good title with a bad branch name 1.
feat: report a skipped check as skipped, not as passed (#537) * feat: report a skipped check as skipped, not as passed A rule that never ran was reported as a pass. commit-check-action#258 is the visible cost: every check on it rendered as a green tick and the summary announced "All 5 checks passed", when in fact nothing had been validated -- the author is dependabot[bot], which the org config lists in ignore_authors. A bypassed policy was indistinguishable from an enforced one, in the JSON, in the Python API, and in anything rendering them. Measured on that exact case before the change: every rule came back "status": "pass" with "value": "", the empty value being the only trace that a skip had happened, and an incidental one at that. Adds ValidationResult.SKIP and returns it from the guards that already decide this -- _should_skip_commit_validation, _should_skip_branch_ validation, and the ignored-author branch of _validate_author. Those helpers are named for skipping; they were simply reporting it as PASS. validate_all_detailed maps SKIP to "skip" and forces the value empty, since a rule that did not run examined nothing. Overall status is "skip" only when every check skipped; one real verdict still yields "pass" or "fail". Only "fail" is an error, so the exit code is unchanged for existing callers and code branching on status == "fail" keeps working. The overall-status rule was duplicated between the CLI's --format json and the Python API, which is how the CLI kept printing "pass" for a fully skipped run after the API had been fixed. It now lives once, in engine.overall_status(), used by both. That also fixes a latent bug in the CLI's exit code: `0 if overall == "pass" else 1` would have turned a skipped run into a failure. Verified end to end in a repository shaped like #258 -- same repo, same config, only the author differing: dependabot[bot] -> overall "skip", every check "skip", exit 0 a human -> overall "pass", values reported, exit 0 a human, bad msg -> exit 1 The twelve existing tests that asserted PASS on these paths are all named for skipping (ignored_author, skips_validation, skip_conditions); they now assert SKIP. Four new API tests pin the behaviour, including a control that only the author differs so the skip test cannot pass by the rules having quietly stopped running for everyone. Reverting the skip reporting turns the first of them red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn * fix: preserve skip when the API merges checks from several runs Review caught a third and fourth copy of the reduce-to-overall rule that the first commit missed. validate_author(name=..., email=...) merges two separate runs, and validate_all() merges up to three; each combined its checks with a private `"fail" if any(...) else "pass"`, so a call in which every nested check skipped still reported "pass" -- the exact defect the skip status exists to prevent, surviving in the two entry points most likely to be called by automation. overall_status() now takes plain status strings rather than CheckOutcome objects, which is what lets every caller share it: the CLI, _build_result, and both combined paths, which hold already-serialised dicts. Four copies of this rule is how it drifted in the first place, so there is now one. Both new tests fail if the per-path rule is restored. Also covers the two skip branches codecov flagged, in BodyValidator and in CommitTypeValidator's non-ignore_authors path. Each comes with a control that changes only the author, so neither can pass by the rule having quietly stopped running for everyone. No line added by this PR is left uncovered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
docs: refresh README sample output to match what commit-check prints (#… …535) The README's output blocks predate rule IDs, so they showed neither the CCxxx identifiers nor the Docs links that every failure now prints. Same staleness #530 fixed in the demo GIF; the docs site was already current, only the README had been left behind. Measured by running each documented command against the checkout: Type message check failed ==> ... -> CC001 message check failed ==> ... Type branch check failed ==> ... -> CC201 branch check failed ==> ... plus a trailing Docs: https://commit-check.com/rules/#ccNNN line on both, and two commit types the list had never picked up (perf, build). Four more blocks were stale the same way: --no-banner carried an "It doesn't match regex:" line that no longer exists in the source, --compact now prints the rule id, both --format json examples were missing rule_id and docs_url and named a subject_imperative check the default run does not emit (it reports subject_max_length and subject_min_length), and the Python API return-value schema was missing rule_id and docs_url. Every block was re-captured and compared byte for byte against the committed text, so these are transcripts rather than transcriptions.
PreviousNext