From 936b2b76e4696e7f74afd3932777eddfb2c07e23 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Tue, 3 Feb 2026 02:08:57 +0200 Subject: [PATCH 1/6] feat: add support for commit message file argument for pre-commit compatibility --- commit_check/main.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/commit_check/main.py b/commit_check/main.py index 02d8cf2e..f2f61049 100644 --- a/commit_check/main.py +++ b/commit_check/main.py @@ -47,6 +47,13 @@ def _get_parser() -> argparse.ArgumentParser: help="path to config file (cchk.toml or commit-check.toml). If not specified, searches for config in: cchk.toml, commit-check.toml, .github/cchk.toml, .github/commit-check.toml", ) + parser.add_argument( + "commit_msg_file", + nargs="?", + default=None, + help="path to commit message file (positional argument for pre-commit compatibility)", + ) + # Main check type arguments check_group = parser.add_argument_group( "check types", "Specify which validation checks to run" @@ -55,9 +62,8 @@ def _get_parser() -> argparse.ArgumentParser: check_group.add_argument( "-m", "--message", - nargs="?", - const="", - help="validate commit message. Optionally specify file path, otherwise reads from stdin if available", + action="store_true", + help="validate commit message (file path can be provided as positional argument for pre-commit compatibility)", ) check_group.add_argument( @@ -310,6 +316,16 @@ def main() -> int: rule_builder = RuleBuilder(config_data) all_rules = rule_builder.build_all_rules() + # Handle positional commit_msg_file argument for pre-commit compatibility + # If commit_msg_file is provided and --message flag is set, use the file path + if args.commit_msg_file and args.message: + args.message = args.commit_msg_file + elif args.commit_msg_file and not any( + [args.branch, args.author_name, args.author_email] + ): + # If only positional arg provided without other check flags, enable message checking + args.message = args.commit_msg_file + # Filter rules based on CLI arguments requested_checks = [] if ( From 9eb7877d42903363144e393f71bb33ac6bf90f7a Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Tue, 3 Feb 2026 02:19:34 +0200 Subject: [PATCH 2/6] fix: refactor commit message handling --- commit_check/imperatives.py | 1 + commit_check/main.py | 33 ++++++++++++++------------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/commit_check/imperatives.py b/commit_check/imperatives.py index 4444785c..00b0e905 100644 --- a/commit_check/imperatives.py +++ b/commit_check/imperatives.py @@ -171,6 +171,7 @@ "read", "record", "redesign", + "refactor", "refer", "refresh", "register", diff --git a/commit_check/main.py b/commit_check/main.py index f2f61049..d825e4e1 100644 --- a/commit_check/main.py +++ b/commit_check/main.py @@ -317,20 +317,17 @@ def main() -> int: all_rules = rule_builder.build_all_rules() # Handle positional commit_msg_file argument for pre-commit compatibility - # If commit_msg_file is provided and --message flag is set, use the file path - if args.commit_msg_file and args.message: - args.message = args.commit_msg_file - elif args.commit_msg_file and not any( - [args.branch, args.author_name, args.author_email] - ): - # If only positional arg provided without other check flags, enable message checking - args.message = args.commit_msg_file + # Store the file path separately from the boolean flag + commit_msg_file_path = None + if args.commit_msg_file: + commit_msg_file_path = args.commit_msg_file + # If a file was provided positionally, enable message checking + if not any([args.branch, args.author_name, args.author_email]): + args.message = True # Filter rules based on CLI arguments requested_checks = [] - if ( - args.message is not None - ): # Check for None explicitly since empty string is valid + if args.message: # args.message is now a boolean flag # Add commit message related checks requested_checks.extend( [ @@ -370,18 +367,16 @@ def main() -> int: stdin_content = None commit_file_path = None - if ( - args.message is not None - ): # Check explicitly for None since empty string is valid - if args.message == "": - # Only set stdin_content if there's actual piped input + if args.message: # args.message is a boolean flag + # Check if we have a file path from positional argument + if commit_msg_file_path: + commit_file_path = commit_msg_file_path + else: + # No file path provided, try reading from stdin stdin_content = stdin_reader.read_piped_input() if not stdin_content: # No stdin and no file - let validators get data from git themselves stdin_content = None - else: - # Message is a file path - commit_file_path = args.message elif not any([args.branch, args.author_name, args.author_email]): # If no specific validation type is requested, don't read stdin pass From 732bbdad9b1b5e29afa8b495d5c6cb7d7a5610a9 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Tue, 3 Feb 2026 02:21:10 +0200 Subject: [PATCH 3/6] fix: accept both hyphen and underscore formats for CLI args --- commit_check/main.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/commit_check/main.py b/commit_check/main.py index d825e4e1..a96c908b 100644 --- a/commit_check/main.py +++ b/commit_check/main.py @@ -105,6 +105,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--conventional-commits", + "--conventional_commits", type=parse_bool, default=None, metavar="BOOL", @@ -113,6 +114,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--subject-capitalized", + "--subject_capitalized", type=parse_bool, default=None, metavar="BOOL", @@ -121,6 +123,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--subject-imperative", + "--subject_imperative", type=parse_bool, default=None, metavar="BOOL", @@ -129,6 +132,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--subject-max-length", + "--subject_max_length", type=parse_int, default=None, metavar="INT", @@ -137,6 +141,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--subject-min-length", + "--subject_min_length", type=parse_int, default=None, metavar="INT", @@ -145,6 +150,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-commit-types", + "--allow_commit_types", type=parse_list, default=None, metavar="LIST", @@ -153,6 +159,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-merge-commits", + "--allow_merge_commits", type=parse_bool, default=None, metavar="BOOL", @@ -161,6 +168,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-revert-commits", + "--allow_revert_commits", type=parse_bool, default=None, metavar="BOOL", @@ -169,6 +177,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-empty-commits", + "--allow_empty_commits", type=parse_bool, default=None, metavar="BOOL", @@ -177,6 +186,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-fixup-commits", + "--allow_fixup_commits", type=parse_bool, default=None, metavar="BOOL", @@ -185,6 +195,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-wip-commits", + "--allow_wip_commits", type=parse_bool, default=None, metavar="BOOL", @@ -193,6 +204,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--require-body", + "--require_body", type=parse_bool, default=None, metavar="BOOL", @@ -201,6 +213,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--require-signed-off-by", + "--require_signed_off_by", type=parse_bool, default=None, metavar="BOOL", @@ -209,6 +222,7 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--ignore-authors", + "--ignore_authors", type=parse_list, default=None, metavar="LIST", @@ -222,6 +236,7 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--conventional-branch", + "--conventional_branch", type=parse_bool, default=None, metavar="BOOL", @@ -230,6 +245,7 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--allow-branch-types", + "--allow_branch_types", type=parse_list, default=None, metavar="LIST", @@ -238,6 +254,7 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--allow-branch-names", + "--allow_branch_names", type=parse_list, default=None, metavar="LIST", @@ -246,6 +263,7 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--require-rebase-target", + "--require_rebase_target", type=str, default=None, metavar="BRANCH", @@ -254,6 +272,7 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--branch-ignore-authors", + "--branch_ignore_authors", type=parse_list, default=None, metavar="LIST", From b312f54dea1da3b7197e8c59ee8074bf71b7aee4 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Tue, 3 Feb 2026 02:34:20 +0200 Subject: [PATCH 4/6] Revert "fix: accept both hyphen and underscore formats for CLI args" This reverts commit 732bbdad9b1b5e29afa8b495d5c6cb7d7a5610a9. --- commit_check/main.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/commit_check/main.py b/commit_check/main.py index a96c908b..d825e4e1 100644 --- a/commit_check/main.py +++ b/commit_check/main.py @@ -105,7 +105,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--conventional-commits", - "--conventional_commits", type=parse_bool, default=None, metavar="BOOL", @@ -114,7 +113,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--subject-capitalized", - "--subject_capitalized", type=parse_bool, default=None, metavar="BOOL", @@ -123,7 +121,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--subject-imperative", - "--subject_imperative", type=parse_bool, default=None, metavar="BOOL", @@ -132,7 +129,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--subject-max-length", - "--subject_max_length", type=parse_int, default=None, metavar="INT", @@ -141,7 +137,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--subject-min-length", - "--subject_min_length", type=parse_int, default=None, metavar="INT", @@ -150,7 +145,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-commit-types", - "--allow_commit_types", type=parse_list, default=None, metavar="LIST", @@ -159,7 +153,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-merge-commits", - "--allow_merge_commits", type=parse_bool, default=None, metavar="BOOL", @@ -168,7 +161,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-revert-commits", - "--allow_revert_commits", type=parse_bool, default=None, metavar="BOOL", @@ -177,7 +169,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-empty-commits", - "--allow_empty_commits", type=parse_bool, default=None, metavar="BOOL", @@ -186,7 +177,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-fixup-commits", - "--allow_fixup_commits", type=parse_bool, default=None, metavar="BOOL", @@ -195,7 +185,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--allow-wip-commits", - "--allow_wip_commits", type=parse_bool, default=None, metavar="BOOL", @@ -204,7 +193,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--require-body", - "--require_body", type=parse_bool, default=None, metavar="BOOL", @@ -213,7 +201,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--require-signed-off-by", - "--require_signed_off_by", type=parse_bool, default=None, metavar="BOOL", @@ -222,7 +209,6 @@ def _get_parser() -> argparse.ArgumentParser: commit_group.add_argument( "--ignore-authors", - "--ignore_authors", type=parse_list, default=None, metavar="LIST", @@ -236,7 +222,6 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--conventional-branch", - "--conventional_branch", type=parse_bool, default=None, metavar="BOOL", @@ -245,7 +230,6 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--allow-branch-types", - "--allow_branch_types", type=parse_list, default=None, metavar="LIST", @@ -254,7 +238,6 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--allow-branch-names", - "--allow_branch_names", type=parse_list, default=None, metavar="LIST", @@ -263,7 +246,6 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--require-rebase-target", - "--require_rebase_target", type=str, default=None, metavar="BRANCH", @@ -272,7 +254,6 @@ def _get_parser() -> argparse.ArgumentParser: branch_group.add_argument( "--branch-ignore-authors", - "--branch_ignore_authors", type=parse_list, default=None, metavar="LIST", From e593590892a9604387f428f8142a09f8fd6db4ef Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Tue, 3 Feb 2026 03:08:13 +0200 Subject: [PATCH 5/6] fix: Update commit_check/main.py per review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- commit_check/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/commit_check/main.py b/commit_check/main.py index d825e4e1..cad9cca3 100644 --- a/commit_check/main.py +++ b/commit_check/main.py @@ -321,9 +321,8 @@ def main() -> int: commit_msg_file_path = None if args.commit_msg_file: commit_msg_file_path = args.commit_msg_file - # If a file was provided positionally, enable message checking - if not any([args.branch, args.author_name, args.author_email]): - args.message = True + # If a file was provided positionally, always enable message checking + args.message = True # Filter rules based on CLI arguments requested_checks = [] From 8adb4db88cf27eed86d863956ca982a1bdceb308 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 03:15:53 +0200 Subject: [PATCH 6/6] test: add test coverage for positional commit_msg_file argument (#367) --- tests/main_test.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tests/main_test.py b/tests/main_test.py index 71da25ca..d67715a7 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -474,3 +474,90 @@ def test_env_overrides_default(self, mocker, monkeypatch): sys.argv = ["commit-check", "--message"] result = main() assert result == 1 # Env var wins, should fail + + +class TestPositionalArgumentFeature: + """Test positional commit_msg_file argument for pre-commit compatibility.""" + + @pytest.mark.benchmark + def test_positional_arg_without_message_flag(self): + """Test using just the positional argument without --message flag.""" + with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: + f.write("feat: add positional argument support") + f.flush() + + try: + # Use positional argument only (no --message flag) + sys.argv = ["commit-check", f.name] + result = main() + assert result == 0 # Should pass validation + finally: + os.unlink(f.name) + + @pytest.mark.benchmark + def test_positional_arg_with_message_flag(self): + """Test using positional argument with --message flag.""" + with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: + f.write("fix: resolve bug in validation") + f.flush() + + try: + # Use both positional argument and --message flag + sys.argv = ["commit-check", "--message", f.name] + result = main() + assert result == 0 # Should pass validation + finally: + os.unlink(f.name) + + @pytest.mark.benchmark + def test_positional_arg_with_branch_flag(self, mocker): + """Test positional argument with other check flags (edge case).""" + # Mock git command to return a valid branch name + mocker.patch( + "subprocess.run", + return_value=type( + "MockResult", (), {"stdout": "feature/test-branch", "returncode": 0} + )(), + ) + + with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: + f.write("chore: update documentation") + f.flush() + + try: + # Use positional argument with --branch flag + sys.argv = ["commit-check", "--branch", f.name] + result = main() + # Should validate both commit message and branch name + assert result == 0 # Should pass both validations + finally: + os.unlink(f.name) + + @pytest.mark.benchmark + def test_positional_arg_invalid_commit(self): + """Test that positional argument correctly rejects invalid commits.""" + with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: + f.write("invalid commit message without type") + f.flush() + + try: + # Use positional argument with invalid message + sys.argv = ["commit-check", f.name] + result = main() + assert result == 1 # Should fail validation + finally: + os.unlink(f.name) + + @pytest.mark.benchmark + def test_positional_arg_nonexistent_file(self, mocker): + """Test that positional argument with non-existent file falls back to git.""" + # Mock git to return a valid commit message + mocker.patch( + "commit_check.engine.get_commit_info", + return_value="feat: add fallback commit from git", + ) + + sys.argv = ["commit-check", "/nonexistent/commit_msg.txt"] + result = main() + # Should fall back to git and pass + assert result == 0