feat: add --rev, and stop the CLI hanging or passing in silence - #544
Conversation
📝 WalkthroughWalkthroughThe PR adds explicit commit revision support to validation, updates skip-state handling and reporting, and prevents blocking reads from idle POSIX stdin. The CLI validates revisions early and passes them into the validation context. ChangesRevision validation flow
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant Git
participant ValidationEngine
participant Output
CLI->>Git: resolve --rev
Git-->>CLI: return commit or error
CLI->>ValidationEngine: validate revision context
ValidationEngine-->>Output: return validation result and skipped checks
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #544 +/- ##
=======================================
Coverage ? 98.13%
=======================================
Files ? 12
Lines ? 1337
Branches ? 0
=======================================
Hits ? 1312
Misses ? 25
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@commit_check/engine.py`:
- Around line 399-401: Update the subject classification checks in
commit_check/engine.py at lines 399-401, 437-439, and 490-492 to use one
consistent canonical Git merge-subject predicate, so ordinary subjects like
“merge settings” are validated. At lines 399-401 and 490-492, match only
canonical merge subjects; at lines 437-439, reuse that predicate and require the
exact “fixup!” prefix.
- Around line 1074-1084: Update the skipped-result reporting in the `if skipped`
block to avoid claiming that nothing was validated when other checks returned
PASS or FAIL. Use neutral wording such as “skipped checks:” for mixed outcomes,
or emit “nothing validated” only when all executed checks skipped.
In `@commit_check/main.py`:
- Around line 52-57: Update read_piped_input and its _has_pending_data readiness
flow so it does not call blocking sys.stdin.read() after select confirms input;
read only currently available bytes using a nonblocking descriptor or bounded
framed-input approach. Add a regression test that writes piped data while
keeping the write descriptor open and verifies the method returns without
blocking.
- Around line 519-535: Treat an empty --rev value as absent everywhere the CLI
currently checks args.rev, including revision validation and stdin suppression.
Update the conditions around the args.rev handling block and the related logic
near the ValidationContext setup so only a non-empty revision proceeds as
supplied, while preserving existing behavior for valid revisions and commit
message files.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 58601476-d0f4-43f8-a6f0-4d0960d8a547
📒 Files selected for processing (5)
commit_check/engine.pycommit_check/main.pypyproject.tomltests/engine_test.pytests/main_test.py
commit-check read stdin whenever it was not a tty, for every check type. On a pipe that is open but that nothing will ever write to or close -- which is what stdin looks like under some CI runners and process managers -- read() blocks forever, so 'commit-check --author-name' became a stuck step rather than a failed one. Reproduced: it hangs until killed. Gate the read with select(): piped input is already in the pipe buffer by the time this process starts (and EOF counts as readable), while an idle pipe is not readable and never will be. Windows keeps the historic blocking read, since select() only handles sockets there and the hang has only been observed on POSIX runners. Three regression tests use real pipes rather than mocks: the idle-open pipe returns None promptly, piped content still arrives, /dev/null reads as nothing. The existing tests that fake piped input by mocking sys.stdin.read get an autouse fixture that opens the gate, restoring the semantics those mocks assume.
There was no way to tell commit-check which commit to check. Message checks read HEAD, and the author checks read the local git config before falling back to HEAD's author -- so CI could not iterate a pull request's commits, and a malformed author on any commit passed as long as the identity of whoever ran the check was valid. --rev REVISION names the commit under test. Message checks read that commit's message. Author checks read that commit's author and never the config: an existing commit's identity is a fact about the commit, not about the operator. The revision is verified up front, so a typo is a clear one-line error instead of a missing-message mystery deep in a validator. stdin is not consulted when --rev is given, and combining it with a message file is rejected -- both would name a second subject for the same checks. End-to-end tests run against a real two-commit repository where HEAD is clean and its parent carries both a bad message and a bad author: the verdict follows the revision, the author verdict flips even though the config identity stays valid, and JSON mode reports the named commit's values.
A silent skip is indistinguishable from a pass. That is how a merge
commit at HEAD -- which is what every pull_request checkout points at --
let a bare 'commit-check --message' report success having judged
nothing it was asked about.
The subject rules' merge and fixup bypasses now return SKIP rather than
PASS, matching what those bypasses mean: the rule declined to judge a
machine-written subject, it did not approve it. The same goes for a
message git never supplied. ignore_authors keeps its PASS there, since
it judges the author and had already done so; a SKIP would wrongly read
as the author having been bypassed.
validate_all then prints one stderr line naming every skipped check:
⊘ skipped (nothing validated): subject-max-length, subject-min-length
stderr so that nothing parsing stdout notices; exit codes are unchanged
because a skip is still not a failure. JSON consumers already saw skip
statuses; now the human running the text mode sees them too.
The subject rules bypassed anything starting with "merge" in any case, so an author's own "merge the parser tables" escaped judgement. Git writes "Merge " and "fixup! " exactly; only those forms are machine-written, so only those are declined now. --rev "" slipped past verification (an empty string is falsy) yet reached the engine, where git's fatal message leaked into the checked value with a green exit. Both rev sites now test against None, and the empty string fails early with the same clear error as a bad revision. Also rewords the skip notice to "not validated" and adds the tests codecov flagged as uncovered.
563e459 to
1483eb1
Compare
BodyValidator reads the full message, so the earlier test never touched _get_commit_body's rev branch; the attribution scan does.
|
Merging this PR will not alter performance
Comparing Footnotes
|
Documentation for the engine changes merged in commit-check/commit-check#544, ahead of the v2.15.0 release, plus the release date for v2.14.0. ## Command-line recipes ([example.md](https://github.com/commit-check/commit-check.com/blob/docs/rev-and-skip-notice/docs/example.md)) - **A "From a revision" tab** under message checking: `--rev` takes anything `git rev-parse` understands, errors up front on a revision that does not resolve, and refuses to be combined with a message file or stdin. - **The author-check section now says whose identity is judged**: without `--rev` it is the local git config (right for a hook, wrong for CI); with `--rev` it is that commit's recorded author, and the config is never consulted. - **The range-checking recipe drops the stdin pipe** for `--rev`, which also lets it include the author checks meaningfully, and shows `git rev-list HEAD^1..HEAD^2` for covering exactly a PR's commits. - **A new "When a check is skipped" section**: which rules decline merge subjects and why only git's literal `Merge `/`fixup! ` prefixes qualify, the one-line stderr notice, the JSON `"status": "skip"`, and a warning box about the synthetic-merge-commit trap on `pull_request` checkouts. Both console examples are pasted from real runs of the current engine, not written by hand. ## Changelog - New v2.15.0 (unreleased) entry: `--rev`, the stdin-hang fix, skip visibility, and the tightened merge/fixup bypass. - v2.14.0 stamped with its release date (published 2026-08-12). - Highlights table row for 2.15.0. `mkdocs build -s` passes (social cards disabled locally — the build environment cannot reach fonts.google.com; nothing in this diff touches that path).

Three CLI behaviours that bit this project's own CI while #541 was being built, fixed as three commits. Together they make commit-check behave predictably when a script, a workflow, or an agent drives it rather than a human at a terminal.
1.
commit-check --author-namecould hang foreverstdin was read whenever it was not a tty — for every check type. On a pipe that is open but that nothing will ever write to or close (what stdin looks like under some CI runners and process managers),
read()blocks indefinitely. In a workflow that is a stuck step, not a failed one, which is the worst way to fail.The read is now gated with
select(): genuinely piped input is already in the pipe buffer when the process starts (and EOF counts as readable), while an idle pipe is not readable and never will be. Piping still works —printf '%s' "$msg" | commit-check --messageis unchanged — and Windows keeps the historic path, sinceselect()only handles sockets there.Regression tests use real pipes rather than mocks: idle-open pipe returns promptly, piped content still arrives,
/dev/nullreads as nothing.2. There was no way to say which commit to check
Message checks read HEAD; author checks read the local git config before falling back to HEAD's author. Two consequences:
awith a clean config: exit 0.New
--rev REVISIONnames the commit under test. Message checks read that commit's message; author checks read that commit's author, never the config — an existing commit's identity is a fact about the commit, not about the operator. A revision that doesn't resolve is a clear one-line error up front.--revplus a message file is rejected, and stdin is not consulted, since each would name a second subject for the same checks.This is the revision input that came up in #541's review as an engine gap the workflow couldn't paper over. With it, iterating a PR becomes:
3. Skipped checks were silent, which reads as passed
On a
pull_requestcheckout HEAD is the synthetic merge commit. The subject rules bypass merge subjects — so a barecommit-check --messageexited 0 having judged nothing it was asked about. That silence is what #541 had to engineer around in shell.The merge/fixup bypasses now return
SKIP(the status #537 introduced) instead ofPASS, and text mode prints one stderr line naming every skipped check:Exit codes are unchanged — a skip is still not a failure — and stdout is untouched, so nothing parsing it notices.
ignore_authorskeeps itsPASSon an absent message: it judges the author and had already done so; aSKIPthere would wrongly read as the author having been bypassed.Verification
580 passed; the one failure is the pre-existingconfig_test.py::test_load_config_file_permission_error, which fails on a cleanmainin this environment because the suite runs as root.--rev HEAD^1passes with a deliberately broken local config, proving the config is no longer consulted; the merge-HEAD false-green scenario now prints the skip notice.commit-check --message.Follow-ups (not in this PR)
--revand the skip notice want entries on commit-check.com's configuration and rules pages, plus a changelog entry when this ships (v2.15.0 — the--revaddition makes it a minor).--rev.Summary by CodeRabbit
New Features
--revto validate a specific Git commit revision.Bug Fixes