-
-
Notifications
You must be signed in to change notification settings - Fork 8
feat: commit-check support reads piped STDIN
#272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Warning Rate limit exceeded@shenxianpeng has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 45 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (3)
WalkthroughAdds optional Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant CLI as CLI
participant Main as main.py
participant Branch as branch.check_branch
participant Author as author.check_author
participant Commit as commit.check_*
Note over CLI,Main: Optional piped STDIN (branch/author/commit message)
CLI->>Main: invoke commit-check (with or without STDIN)
Main->>Main: read non-interactive STDIN -> stdin_text or None
Main->>Branch: check_branch(checks, stdin_text)
Branch-->>Main: result
Main->>Author: check_author(checks, which, stdin_text)
Author-->>Main: result
Main->>Commit: check_commit_msg(..., stdin_text)
Commit-->>Main: result
Main->>Commit: check_commit_signoff(..., stdin_text)
Commit-->>Main: result
Main->>Commit: check_imperative(..., stdin_text)
Commit-->>Main: result
Main-->>CLI: aggregate exit code
sequenceDiagram
participant Check as check_*
participant Repo as Git/Files
alt stdin_text provided
Note right of Check: Use stdin_text directly (no repo/file I/O)
Check--x Repo: skip has_commits()/file reads
Check-->>Check: apply regex/validation
else stdin_text is None
Check->>Repo: perform has_commits()/read files/derive values
Check-->>Check: apply regex/validation
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Suggested labels
Poem
✨ Finishing Touches
🧪 Generate unit tests
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❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #272 +/- ##
==========================================
- Coverage 99.13% 97.80% -1.33%
==========================================
Files 8 8
Lines 346 365 +19
==========================================
+ Hits 343 357 +14
- Misses 3 8 +5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Co-authored-by: Copilot <[email protected]>
CodSpeed Performance ReportMerging #272 will improve performances by 29.68%Comparing Summary
Benchmarks breakdown
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
commit_check/commit.py (1)
97-105: Bug: get_commit_info('H') runs even with stdin override (no repo).When stdin_text is provided, this still calls Git to fetch the hash, which can fail outside a repo. Avoid Git calls in stdin mode.
- commit_hash = get_commit_info("H") + commit_hash = get_commit_info("H") if stdin_text is None else "N/A"
♻️ Duplicate comments (2)
commit_check/branch.py (1)
17-17: Good fix: guard on None instead of truthiness.This addresses the prior AttributeError risk noted earlier.
tests/main_test.py (1)
165-177: Fix: invalid keyword passed to mocker.patch (will raise TypeError)stdin_text is a kwarg to the target function, not to patch(). This breaks test collection/execution. Remove stdin_text=None from patch calls and, if needed, assert on called kwargs instead.
Apply:
- mocker.patch( - "commit_check.commit.check_commit_msg", return_value=message_result, stdin_text=None - ) + mocker.patch("commit_check.commit.check_commit_msg", return_value=message_result) @@ - mocker.patch( - "commit_check.commit.check_commit_signoff", - return_value=commit_signoff_result, stdin_text=None - ) + mocker.patch("commit_check.commit.check_commit_signoff", return_value=commit_signoff_result) @@ - mocker.patch("commit_check.branch.check_branch", return_value=branch_result, stdin_text=None) + mocker.patch("commit_check.branch.check_branch", return_value=branch_result) @@ - mocker.patch( - "commit_check.branch.check_merge_base", return_value=merge_base_result, stdin_text=None - ) + mocker.patch("commit_check.branch.check_merge_base", return_value=merge_base_result) - mocker.patch("commit_check.commit.check_imperative", return_value=PASS, stdin_text=None) + mocker.patch("commit_check.commit.check_imperative", return_value=PASS)Optional: after main(), verify kwargs for the calls that should have occurred:
for m, expected_calls in [ (mocker.get_mock("commit_check.commit.check_commit_msg"), check_commit_call_count), (mocker.get_mock("commit_check.commit.check_commit_signoff"), check_commit_signoff_call_count), (mocker.get_mock("commit_check.branch.check_branch"), check_branch_call_count), (mocker.get_mock("commit_check.commit.check_imperative"), check_imperative_call_count), ]: if expected_calls: assert "stdin_text" in m.call_args.kwargs
🧹 Nitpick comments (7)
commit_check/branch.py (1)
16-18: Clarify behavior for blank/whitespace-only stdin.Current logic treats any provided stdin (even " \n") as the branch name after strip(), yielding "" and likely failing regex. If prior behavior expected empty stdin to fall back to git branch, consider:
- branch_name = stdin_text.strip() if stdin_text is not None else get_branch_name() + if stdin_text is not None: + branch_name = stdin_text.strip() + if branch_name == "": + branch_name = get_branch_name() + else: + branch_name = get_branch_name()commit_check/author.py (1)
20-27: Harden check_type handling and normalize stdin.
- Use .strip() on stdin to avoid newline/space mismatches.
- Make the author name/email selection mutually exclusive and fail loudly on unknown types to avoid UnboundLocalError.
- if stdin_text is not None: - config_value = stdin_text + if stdin_text is not None: + config_value = stdin_text.strip() else: - if check_type == "author_name": - format_str = "an" - if check_type == 'author_email': - format_str = "ae" + if check_type == "author_name": + format_str = "an" + elif check_type == "author_email": + format_str = "ae" + else: + raise ValueError(f"Unsupported check_type: {check_type}") config_value = str(get_commit_info(format_str))commit_check/commit.py (3)
31-46: Skip work when no message checks are configured; minor truthiness nit.
- Add a fast path to avoid reading messages when no 'message' check exists.
- Prefer a simple truthiness check for commit_msg_file.
def check_commit_msg(checks: list, commit_msg_file: str = "", stdin_text: Optional[str] = None) -> int: @@ - if stdin_text is None and has_commits() is False: + if stdin_text is None and has_commits() is False: return PASS # pragma: no cover + # Fast path: if no 'message' check configured, skip reading + if not any(c.get('check') == 'message' for c in checks): + return PASS + if stdin_text is not None: commit_msg = stdin_text else: - if commit_msg_file is None or commit_msg_file == "": + if not commit_msg_file: commit_msg_file = get_default_commit_msg_file() commit_msg = read_commit_msg(commit_msg_file)
70-81: Add fast path for signoff like imperative.Avoid reading commit message when no 'commit_signoff' check is present.
def check_commit_signoff(checks: list, commit_msg_file: str = "", stdin_text: Optional[str] = None) -> int: @@ - if stdin_text is not None: + # Fast path: if no 'commit_signoff' check configured, skip + if not any(c.get('check') == 'commit_signoff' for c in checks): + return PASS + + if stdin_text is not None: commit_msg = stdin_text else: - if commit_msg_file is None or commit_msg_file == "": + if not commit_msg_file: commit_msg_file = get_default_commit_msg_file() commit_msg = read_commit_msg(commit_msg_file)
127-129: Consistency nit: useif not commit_msg_filein all three places.Unify the None/empty checks across functions for readability.
- if commit_msg_file is None or commit_msg_file == "": + if not commit_msg_file: commit_msg_file = get_default_commit_msg_file()Also applies to: 79-80, 43-45
tests/commit_test.py (1)
360-363: LGTM: stdin overrides file read path is not exercised hereThis test asserts early-exit on different check type and ensures read_commit_msg isn’t called. Consider adding a separate test that actually exercises the imperative path using stdin_text (e.g., “feat: Add …”) and asserts read_commit_msg is not used.
Example new test (outside this hunk):def test_check_imperative_uses_stdin_when_provided(mocker): checks = [{"check": "imperative", "regex": "", "error": "", "suggest": ""}] m_read = mocker.patch("commit_check.commit.read_commit_msg") assert check_imperative(checks, None, stdin_text="feat: Add feature") == PASS m_read.assert_not_called()commit_check/main.py (1)
128-130: Deduplicate validate_config callvalidate_config is invoked twice; cache the result.
- config = validate_config(args.config) if validate_config( - args.config, - ) else DEFAULT_CONFIG + _cfg = validate_config(args.config) + config = _cfg if _cfg else DEFAULT_CONFIG
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
commit_check/author.py(2 hunks)commit_check/branch.py(1 hunks)commit_check/commit.py(4 hunks)commit_check/main.py(3 hunks)tests/commit_test.py(3 hunks)tests/main_test.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
commit_check/author.py (1)
tests/author_test.py (5)
test_check_author_with_different_check(73-90)test_check_author_with_different_check(188-205)test_check_author_with_empty_checks(56-70)test_check_author_with_empty_checks(171-185)test_check_author(16-33)
commit_check/branch.py (1)
tests/branch_test.py (4)
TestCheckBranch(10-120)test_check_branch_with_empty_checks(32-46)test_check_branch_with_different_check(49-66)test_check_branch(12-29)
tests/main_test.py (1)
tests/author_test.py (7)
test_check_author_with_empty_checks(171-185)test_check_author_with_different_check(73-90)test_check_author_with_different_check(188-205)TestAuthor(9-260)test_check_author_with_empty_checks(56-70)TestAuthorEmail(146-260)test_check_author(151-168)
🔇 Additional comments (9)
commit_check/branch.py (1)
3-3: Type hint import looks good.commit_check/author.py (2)
3-3: Type hint import is fine.
8-11: Gating works as intended for stdin override.Proceeding when stdin_text is provided and short-circuiting otherwise is correct.
commit_check/commit.py (2)
2-2: Type hint import is fine.
118-121: Nice: fast path prevents unnecessary IO.tests/commit_test.py (2)
343-345: LGTM: explicit stdin passthrough for merge-commit skipPassing stdin_text=None keeps behavior unchanged while exercising the new parameter. Looks good.
391-393: LGTM: empty checks short-circuit without file readNice guard; verifies no I/O when checks are empty.
tests/main_test.py (1)
178-180: LGTM: author mock tolerates extra kwargsAccepting **kwargs avoids brittle test failures as signatures evolve.
commit_check/main.py (1)
132-147: LGTM: consistent stdin_text propagationPassing stdin_text through all relevant checks matches the new API and keeps behavior cohesive.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
commit_check/commit.py (1)
97-105: Bug: avoid git dependency in stdin-only mode for signoff checkWhen
stdin_textis provided (running outside a git repo),get_commit_info("H")can fail. Use a placeholder instead.Apply:
- commit_hash = get_commit_info("H") + # Avoid git dependency when using stdin-only mode + commit_hash = get_commit_info("H") if stdin_text is None else "<stdin>"Would you like a small test covering
check_commit_signoffwithstdin_textand no repo?
♻️ Duplicate comments (1)
commit_check/branch.py (1)
16-16: Good fix: guard on None rather than truthiness for stdin_text
stdin_text.strip() if stdin_text is not Nonecorrectly handles empty strings and resolves the prior review concern.
🧹 Nitpick comments (3)
commit_check/commit.py (1)
118-125: Optional: fast-exit and truly lazy message loading in imperative checkYou always read the commit message even when no
imperativerules are configured. Early-return to skip I/O, then keep the existing lazy read. Note: this changes current test expectations (one test asserts a read on empty checks).def check_imperative(checks: list, commit_msg_file: str = "", stdin_text: Optional[str] = None) -> int: """Check if commit message uses imperative mood.""" if stdin_text is None and has_commits() is False: return PASS # pragma: no cover + + # Fast-exit if no imperative rules configured + if not any(c.get('check') == 'imperative' for c in checks): + return PASS @@ - # Lazily obtain commit message only when needed - if stdin_text is not None: - commit_msg = stdin_text - else: - if commit_msg_file is None or commit_msg_file == "": - commit_msg_file = get_default_commit_msg_file() - commit_msg = read_commit_msg(commit_msg_file) + # Lazily obtain commit message only when needed + if stdin_text is not None: + commit_msg = stdin_text + else: + if not commit_msg_file: + commit_msg_file = get_default_commit_msg_file() + commit_msg = read_commit_msg(commit_msg_file)Also applies to: 126-153
commit_check/main.py (2)
123-124: Remove redundant second dry-run checkThere’s already an early return at Line 112.
- if args.dry_run: - return PASS
8-8: Narrow the broad except when capturing stdinCatching
Exceptionis too wide. Limit to expected I/O errors and add the import.@@ -import sys +import sys +import io @@ - except Exception: + except (OSError, io.UnsupportedOperation): stdin_text = NoneOptionally, only read stdin when a stdin-aware check is requested (
message,author_*,branch,commit_signoff,imperative) to avoid surprising reads for--merge-base-only runs. Happy to draft a follow-up diff if you want it.Also applies to: 121-122
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
commit_check/branch.py(1 hunks)commit_check/commit.py(4 hunks)commit_check/main.py(3 hunks)tests/commit_test.py(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
commit_check/branch.py (2)
commit_check/util.py (4)
get_branch_name(15-30)print_error_header(128-144)print_error_message(147-159)print_suggestion(162-173)tests/branch_test.py (3)
TestCheckBranch(10-120)test_check_branch_with_empty_checks(32-46)test_check_branch_with_different_check(49-66)
commit_check/main.py (3)
commit_check/commit.py (3)
check_commit_msg(31-67)check_commit_signoff(70-110)check_imperative(113-153)commit_check/author.py (1)
check_author(8-39)commit_check/branch.py (1)
check_branch(8-28)
commit_check/commit.py (1)
commit_check/util.py (1)
has_commits(33-46)
tests/commit_test.py (1)
commit_check/commit.py (1)
check_imperative(113-153)
🪛 Ruff (0.12.2)
commit_check/main.py
121-121: Do not catch blind exception: Exception
(BLE001)
tests/commit_test.py
392-392: Use of assert detected
(S101)
393-393: Use of assert detected
(S101)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: install (3.13, windows-latest)
- GitHub Check: install (3.10, macos-latest)
- GitHub Check: install (3.11, windows-latest)
- GitHub Check: install (3.12, windows-latest)
- GitHub Check: install (3.10, windows-latest)
- GitHub Check: install (3.9, macos-latest)
- GitHub Check: install (3.9, windows-latest)
- GitHub Check: Run benchmarks
🔇 Additional comments (1)
tests/commit_test.py (1)
343-345: Tests align with new stdin_text API
- Using
stdin_text=Nonekeeps old path intact.- Passing
stdin_text="..."ensures no file read.- Empty-checks case documents current behavior (read occurs); if you adopt the fast-exit refactor, update this assertion.
Also applies to: 360-363, 391-394
commit-check support check from stdincommit-check can be supplied directly via STDIN
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
commit_check/author.py (3)
26-28: Treat blank stdin as “not provided” and trim inputsWhitespace-only stdin currently forces validation of an empty string. Prefer trimming and falling back to git-derived value when stdin is blank; also strip git value to avoid newline artifacts.
- if stdin_text is None and has_commits() is False: + if (stdin_text is None || (isinstance(stdin_text, str) and not stdin_text.strip())) and has_commits() is False: return PASS # pragma: no cover @@ - if stdin_text is not None: - value = stdin_text - else: - value = _get_author_value(check_type) + if stdin_text is not None and stdin_text.strip(): + value = stdin_text.strip() + else: + value = _get_author_value(check_type).strip()Also applies to: 40-43
35-38: Polish user-facing messageSmall grammar fix for clarity.
- print(f"{YELLOW}Not found regex for {check_type}. skip checking.{RESET_COLOR}") + print(f"{YELLOW}No regex found for {check_type}; skipping.{RESET_COLOR}")
45-45: Use fullmatch to prevent partial matchesre.match allows prefix matches; fullmatch enforces complete validation (useful for strict author name/email formats).
- if re.match(regex, value): + if re.fullmatch(regex, value):
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
commit_check/author.py(3 hunks)commit_check/branch.py(2 hunks)commit_check/commit.py(6 hunks)tests/commit_test.py(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- commit_check/commit.py
- commit_check/branch.py
- tests/commit_test.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-07T15:16:47.288Z
Learnt from: CR
PR: commit-check/commit-check#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-07T15:16:47.288Z
Learning: Ensure author information in commits is valid: proper author name and email formats
Applied to files:
commit_check/author.py
🧬 Code graph analysis (1)
commit_check/author.py (1)
commit_check/util.py (1)
has_commits(54-67)
🪛 Ruff (0.12.2)
commit_check/author.py
25-25: SyntaxError: Got unexpected token ,
🪛 GitHub Actions: main
commit_check/author.py
[error] 25-25: Sphinx configuration error: invalid character ',' (U+FF0C) found in author.py at line 25.
🔇 Additional comments (1)
commit_check/author.py (1)
3-3: Import Optional — LGTMType import is correct and scoped to the new param.
commit-check can be supplied directly via STDINcommit-check support reads piped STDIN
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for reading piped STDIN input to the commit-check CLI tool, allowing validations to run without requiring a Git repository. The implementation enables commit message, author information, and branch name checks to accept values via STDIN while maintaining backward compatibility with existing file-based workflows.
Key changes:
- Enhanced CLI to capture and forward STDIN input to validation functions
- Updated all check functions to accept optional
stdin_textparameter - Modified Git repository dependency logic to allow STDIN-driven validation
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| commit_check/main.py | Added STDIN capture logic and modified all check function calls to pass stdin_text parameter |
| commit_check/commit.py | Updated commit validation functions to accept and use stdin_text when provided |
| commit_check/branch.py | Modified branch check to use stdin_text for branch name validation |
| commit_check/author.py | Enhanced author validation to support stdin_text input |
| tests/main_test.py | Updated test mocks to include new stdin_text parameter |
| tests/commit_test.py | Modified test calls to include stdin_text parameter |
| .pre-commit-config.yaml | Updated ruff version and removed requirements-txt-fixer hook |
| return PASS | ||
|
|
||
| branch_name = get_branch_name() | ||
| branch_name = stdin_text.strip() if stdin_text is not None else get_branch_name() |
Copilot
AI
Sep 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line will raise an AttributeError if stdin_text is an empty string, since empty strings are not None but calling .strip() on None would fail. Should check for both None and empty string: stdin_text.strip() if stdin_text else get_branch_name()
| branch_name = stdin_text.strip() if stdin_text is not None else get_branch_name() | |
| branch_name = stdin_text.strip() if stdin_text is not None and stdin_text.strip() != "" else get_branch_name() |
Co-authored-by: Copilot <[email protected]>
|



Summary by CodeRabbit
New Features
Bug Fixes
Tests