From a81f66a503189dbb30607c17fe578bfd47bee8fb Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 3 Jun 2025 20:13:03 +0800 Subject: [PATCH 1/2] feat(check): add check against default branch --- commitizen/cli.py | 7 ++++ commitizen/commands/check.py | 34 ++++++++++++------- commitizen/git.py | 7 ++++ ...shows_description_when_use_help_option.txt | 4 ++- tests/test_git.py | 19 +++++++++++ 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/commitizen/cli.py b/commitizen/cli.py index 6f7556df49..b62f25ee8f 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -474,6 +474,13 @@ def __call__( "help": "a range of git rev to check. e.g, master..HEAD", "exclusive_group": "group1", }, + { + "name": ["-d", "--default-range"], + "action": "store_true", + "default": False, + "help": "check from the default branch to HEAD. e.g, refs/remotes/origin/master..HEAD", + "exclusive_group": "group1", + }, { "name": ["-m", "--message"], "help": "commit message that needs to be checked", diff --git a/commitizen/commands/check.py b/commitizen/commands/check.py index 69147bcfbe..6eb6646865 100644 --- a/commitizen/commands/check.py +++ b/commitizen/commands/check.py @@ -21,6 +21,7 @@ class CheckArgs(TypedDict, total=False): message_length_limit: int allowed_prefixes: list[str] message: str + default_range: bool class Check: @@ -40,6 +41,7 @@ def __init__(self, config: BaseConfig, arguments: CheckArgs, *args: object) -> N self.allow_abort = bool( arguments.get("allow_abort", config.settings["allow_abort"]) ) + self.default_range = bool(arguments.get("default_range")) self.max_msg_length = arguments.get("message_length_limit", 0) # we need to distinguish between None and [], which is a valid value @@ -50,25 +52,28 @@ def __init__(self, config: BaseConfig, arguments: CheckArgs, *args: object) -> N else config.settings["allowed_prefixes"] ) - self._valid_command_argument() - - self.config: BaseConfig = config - self.encoding = config.settings["encoding"] - self.cz = factory.committer_factory(self.config) - - def _valid_command_argument(self) -> None: num_exclusive_args_provided = sum( arg is not None - for arg in (self.commit_msg_file, self.commit_msg, self.rev_range) + for arg in ( + self.commit_msg_file, + self.commit_msg, + self.rev_range, + ) ) - if num_exclusive_args_provided == 0 and not sys.stdin.isatty(): - self.commit_msg = sys.stdin.read() - elif num_exclusive_args_provided != 1: + + if num_exclusive_args_provided > 1: raise InvalidCommandArgumentError( "Only one of --rev-range, --message, and --commit-msg-file is permitted by check command! " "See 'cz check -h' for more information" ) + if num_exclusive_args_provided == 0 and not sys.stdin.isatty(): + self.commit_msg = sys.stdin.read() + + self.config: BaseConfig = config + self.encoding = config.settings["encoding"] + self.cz = factory.committer_factory(self.config) + def __call__(self) -> None: """Validate if commit messages follows the conventional pattern. @@ -109,7 +114,10 @@ def _get_commits(self) -> list[git.GitCommit]: return [git.GitCommit(rev="", title="", body=self._filter_comments(msg))] # Get commit messages from git log (--rev-range) - return git.get_commits(end=self.rev_range) + return git.get_commits( + git.get_default_branch() if self.default_range else None, + self.rev_range, + ) @staticmethod def _filter_comments(msg: str) -> str: @@ -134,7 +142,7 @@ def _filter_comments(msg: str) -> str: The filtered commit message without comments. """ - lines = [] + lines: list[str] = [] for line in msg.split("\n"): if "# ------------------------ >8 ------------------------" in line: break diff --git a/commitizen/git.py b/commitizen/git.py index 8025041abb..615d6aaf27 100644 --- a/commitizen/git.py +++ b/commitizen/git.py @@ -331,3 +331,10 @@ def _get_log_as_str_list(start: str | None, end: str, args: str) -> list[str]: if not c.out: return [] return c.out.split(f"{delimiter}\n") + + +def get_default_branch() -> str: + c = cmd.run("git symbolic-ref refs/remotes/origin/HEAD") + if c.return_code != 0: + raise GitCommandError(c.err) + return c.out.strip() diff --git a/tests/commands/test_check_command/test_check_command_shows_description_when_use_help_option.txt b/tests/commands/test_check_command/test_check_command_shows_description_when_use_help_option.txt index 85f42f6d2a..69563cbc64 100644 --- a/tests/commands/test_check_command/test_check_command_shows_description_when_use_help_option.txt +++ b/tests/commands/test_check_command/test_check_command_shows_description_when_use_help_option.txt @@ -1,5 +1,5 @@ usage: cz check [-h] [--commit-msg-file COMMIT_MSG_FILE | - --rev-range REV_RANGE | -m MESSAGE] [--allow-abort] + --rev-range REV_RANGE | -d | -m MESSAGE] [--allow-abort] [--allowed-prefixes [ALLOWED_PREFIXES ...]] [-l MESSAGE_LENGTH_LIMIT] @@ -13,6 +13,8 @@ options: MSG_FILE=$1 --rev-range REV_RANGE a range of git rev to check. e.g, master..HEAD + -d, --default-range check from the default branch to HEAD. e.g, + refs/remotes/origin/master..HEAD -m, --message MESSAGE commit message that needs to be checked --allow-abort allow empty commit messages, which typically abort a diff --git a/tests/test_git.py b/tests/test_git.py index e242b3a2ae..86c188cd1c 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -480,3 +480,22 @@ def test_create_commit_cmd_string(mocker, os_name, committer_date, expected_cmd) mocker.patch("os.name", os_name) result = git._create_commit_cmd_string("", committer_date, "temp.txt") assert result == expected_cmd + + +def test_get_default_branch(mocker: MockFixture): + # Test successful case + mocker.patch( + "commitizen.cmd.run", return_value=FakeCommand(out="refs/remotes/origin/main\n") + ) + assert git.get_default_branch() == "refs/remotes/origin/main" + + # Test error case + mocker.patch( + "commitizen.cmd.run", + return_value=FakeCommand( + err="fatal: ref refs/remotes/origin/HEAD is not a symbolic ref", + return_code=1, + ), + ) + with pytest.raises(exceptions.GitCommandError): + git.get_default_branch() From 3c7623f67c5b0ce671b5d496fc8f1de52bb97971 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 13:47:28 +0800 Subject: [PATCH 2/2] docs(check): update --default-branch option --- docs/commands/check.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/commands/check.md b/docs/commands/check.md index 33e41e04f8..a1a5582827 100644 --- a/docs/commands/check.md +++ b/docs/commands/check.md @@ -27,19 +27,21 @@ $ cz check --rev-range REV_RANGE For example, if you'd like to check all commits on a branch, you can use `--rev-range master..HEAD`. Or, if you'd like to check all commits starting from when you first implemented commit message linting, you can use `--rev-range ..HEAD`. +You can also use `--default-range` to check all commits from the default branch up to HEAD. This is equivalent to using `--rev-range ..HEAD`. + For more information on how git commit ranges work, you can check the [git documentation](https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection#_commit_ranges). ### Commit Message There are two ways you can provide your plain message and check it. -#### Method 1: use -m or --message +#### Method 1: use `-m` or `--message` ```bash $ cz check --message MESSAGE ``` -In this option, MESSAGE is the commit message to be checked. +In this option, `MESSAGE` is the commit message to be checked. #### Method 2: use pipe to pipe it to `cz check` @@ -47,7 +49,7 @@ In this option, MESSAGE is the commit message to be checked. $ echo MESSAGE | cz check ``` -In this option, MESSAGE is piped to cz check and will be checked. +In this option, `MESSAGE` is piped to `cz check` and will be checked. ### Commit Message File @@ -55,7 +57,7 @@ In this option, MESSAGE is piped to cz check and will be checked. $ cz check --commit-msg-file COMMIT_MSG_FILE ``` -In this option, COMMIT_MSG_FILE is the path of the temporary file that contains the commit message. +In this option, `COMMIT_MSG_FILE` is the path of the temporary file that contains the commit message. This argument can be useful when cooperating with git hooks. Please check [Automatically check message before commit](../tutorials/auto_check.md) for more information about how to use this argument with git hooks. ### Allow Abort @@ -65,12 +67,18 @@ cz check --message MESSAGE --allow-abort ``` Empty commit messages typically instruct Git to abort a commit, so you can pass `--allow-abort` to -permit them. Since `git commit` accepts an `--allow-empty-message` flag (primarily for wrapper scripts), you may wish to disallow such commits in CI. `--allow-abort` may be used in conjunction with any of the other options. +permit them. Since `git commit` accepts the `--allow-empty-message` flag (primarily for wrapper scripts), you may wish to disallow such commits in CI. `--allow-abort` may be used in conjunction with any of the other options. ### Allowed Prefixes If the commit message starts with some specific prefixes, `cz check` returns `True` without checking the regex. -By default, the following prefixes are allowed: `Merge`, `Revert`, `Pull request`, `fixup!` and `squash!`. +By default, the following prefixes are allowed: + +- `Merge` +- `Revert` +- `Pull request` +- `fixup!` +- `squash!` ```bash cz check --message MESSAGE --allowed-prefixes 'Merge' 'Revert' 'Custom Prefix' @@ -83,5 +91,5 @@ For example, `cz check --message MESSAGE -l 3` would fail the check, since `MESS By default, the limit is set to 0, which means no limit on the length. **Note that the limit applies only to the first line of the message.** -Specifically, for `ConventionalCommitsCz` the length only counts from the type of change to the subject, -while the body and the footer are not counted. + +Specifically, for `ConventionalCommitsCz` the length only counts from the type of change to the subject, while the body and the footer are not counted.