Repository: commitizen-tools/commitizen

Issue:
enable skiping certain errors like no commit found
## Description
enable skiping certain errors like no commit found

## Possible Solution
in short

```
cz [ops] --no-raise [error type]
```

detailed on [473](https://github.com/commitizen-tools/commitizen/pull/473#issuecomment-1022930540)

## Additional context
[exceptions](https://github.com/commitizen-tools/commitizen/blob/master/commitizen/exceptions.py)

## Related Issue
[473](https://github.com/commitizen-tools/commitizen/pull/473#issuecomment-1022930540)


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/commitizen-tools/commitizen/pull/489?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#489](https://codecov.io/gh/commitizen-tools/commitizen/pull/489?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (f3a8769) into [master](https://codecov.io/gh/commitizen-tools/commitizen/commit/bc9be40cf304ba6935442a55a57cf7c8abbcd7c2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (bc9be40) will **decrease** coverage by `0.26%`.
> The diff coverage is `83.33%`.

[![Impacted file tree graph](https://codecov.io/gh/commitizen-tools/commitizen/pull/489/graphs/tree.svg?width=650&height=150&src=pr&token=7uPVORwy07&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)](https://codecov.io/gh/commitizen-tools/commitizen/pull/489?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

```diff
@@            Coverage Diff             @@
##           master     #489      +/-   ##
==========================================
- Coverage   98.05%   97.79%   -0.27%     
==========================================
  Files          39       39              
  Lines        1441     1450       +9     
==========================================
+ Hits         1413     1418       +5     
- Misses         28       32       +4     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `97.79% <83.33%> (-0.27%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/commitizen-tools/commitizen/pull/489?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [commitizen/cli.py](https://codecov.io/gh/commitizen-tools/commitizen/pull/489/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29tbWl0aXplbi9jbGkucHk=) | `92.15% <81.81%> (-5.47%)` | :arrow_down: |
| [commitizen/\_\_version\_\_.py](https://codecov.io/gh/commitizen-tools/commitizen/pull/489/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29tbWl0aXplbi9fX3ZlcnNpb25fXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [commitizen/changelog.py](https://codecov.io/gh/commitizen-tools/commitizen/pull/489/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29tbWl0aXplbi9jaGFuZ2Vsb2cucHk=) | `95.77% <0.00%> (-0.71%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/commitizen-tools/commitizen/pull/489?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/commitizen-tools/commitizen/pull/489?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Last update [6efad39...f3a8769](https://codecov.io/gh/commitizen-tools/commitizen/pull/489?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).

Woile: @Lee-W I'm wondering whether we should go for:

```sh
cz -nr 1 -nr 2 bump
```

or 

```sh
cz -nr 1,2,3 bump
```

I think for github action would be easier to do the 2nd one and maybe it's simpler. What do you think?
Lee-W: I like `cz -nr 1,2,3 bump` a bit more 👍
Woile: Updated docs and now it works with:

```sh
cz -nr 1,2,3
```

```sh
cz -nr NO_INCREMENT,NO_COMMITS_FOUND
```

Please test 🙏🏻 

Patch:
diff --git a/commitizen/cli.py b/commitizen/cli.py
index 839e6cf5..bf751b44 100644
--- a/commitizen/cli.py
+++ b/commitizen/cli.py
@@ -2,12 +2,18 @@ import argparse
 import logging
 import sys
 from functools import partial
+from typing import List
 
 import argcomplete
 from decli import cli
 
-from commitizen import commands, config
-from commitizen.exceptions import CommitizenException, ExpectedExit, NoCommandFoundError
+from commitizen import commands, config, out
+from commitizen.exceptions import (
+    CommitizenException,
+    ExitCode,
+    ExpectedExit,
+    NoCommandFoundError,
+)
 
 logger = logging.getLogger(__name__)
 data = {
@@ -24,6 +30,12 @@ data = {
             "name": ["-n", "--name"],
             "help": "use the given commitizen (default: cz_conventional_commits)",
         },
+        {
+            "name": ["-nr", "--no-raise"],
+            "type": str,
+            "required": False,
+            "help": "comma separated error codes that won't rise error, e.g: cz -nr 1,2,3 bump. See codes at https://commitizen-tools.github.io/commitizen/exit_codes/",
+        },
     ],
     "subcommands": {
         "title": "commands",
@@ -274,13 +286,20 @@ data = {
 original_excepthook = sys.excepthook
 
 
-def commitizen_excepthook(type, value, tracekback, debug=False):
+def commitizen_excepthook(
+    type, value, tracekback, debug=False, no_raise: List[int] = None
+):
+    if not no_raise:
+        no_raise = []
     if isinstance(value, CommitizenException):
         if value.message:
             value.output_method(value.message)
         if debug:
             original_excepthook(type, value, tracekback)
-        sys.exit(value.exit_code)
+        exit_code = value.exit_code
+        if exit_code in no_raise:
+            exit_code = 0
+        sys.exit(exit_code)
     else:
         original_excepthook(type, value, tracekback)
 
@@ -290,6 +309,28 @@ commitizen_debug_excepthook = partial(commitizen_excepthook, debug=True)
 sys.excepthook = commitizen_excepthook
 
 
+def parse_no_raise(comma_separated_no_raise: str) -> List[int]:
+    """Convert the given string to exit codes.
+
+    Receives digits and strings and outputs the parsed integer which
+    represents the exit code found in exceptions.
+    """
+    no_raise_items: List[str] = comma_separated_no_raise.split(",")
+    no_raise_codes = []
+    for item in no_raise_items:
+        if item.isdecimal():
+            no_raise_codes.append(int(item))
+            continue
+        try:
+            exit_code = ExitCode[item.strip()]
+        except KeyError:
+            out.warn(f"WARN: no_raise key `{item}` does not exist. Skipping.")
+            continue
+        else:
+            no_raise_codes.append(exit_code.value)
+    return no_raise_codes
+
+
 def main():
     conf = config.read_cfg()
     parser = cli(data)
@@ -319,6 +360,12 @@ def main():
     if args.debug:
         logging.getLogger("commitizen").setLevel(logging.DEBUG)
         sys.excepthook = commitizen_debug_excepthook
+    elif args.no_raise:
+        no_raise_exit_codes = parse_no_raise(args.no_raise)
+        no_raise_debug_excepthook = partial(
+            commitizen_excepthook, no_raise=no_raise_exit_codes
+        )
+        sys.excepthook = no_raise_debug_excepthook
 
     args.func(conf, vars(args))()
 
diff --git a/commitizen/exceptions.py b/commitizen/exceptions.py
index 9ae97ee1..a95ab3b5 100644
--- a/commitizen/exceptions.py
+++ b/commitizen/exceptions.py
@@ -25,6 +25,7 @@ class ExitCode(enum.IntEnum):
     INVALID_COMMAND_ARGUMENT = 18
     INVALID_CONFIGURATION = 19
     NOT_ALLOWED = 20
+    NO_INCREMENT = 21
 
 
 class CommitizenException(Exception):
@@ -56,7 +57,7 @@ class DryRunExit(ExpectedExit):
 
 
 class NoneIncrementExit(CommitizenException):
-    exit_code = ExitCode.NO_COMMITS_FOUND
+    exit_code = ExitCode.NO_INCREMENT
 
 
 class NoCommitizenFoundException(CommitizenException):
diff --git a/commitizen/out.py b/commitizen/out.py
index a25f0f2e..811fe09c 100644
--- a/commitizen/out.py
+++ b/commitizen/out.py
@@ -30,3 +30,8 @@ def info(value: str) -> None:
 
 def diagnostic(value: str):
     line(value, file=sys.stderr)
+
+
+def warn(value: str) -> None:
+    message = colored(value, "magenta")
+    line(message)
diff --git a/docs/README.md b/docs/README.md
index 3bb2db55..f459ccfa 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -124,11 +124,9 @@ Read more about the `check` command [here](check.md).
 
 ### Help
 
-```bash
+```sh
 $ cz --help
-usage: cz [-h] [--debug] [-n NAME] [--version]
-          {init,commit,c,ls,example,info,schema,bump,changelog,ch,check,version}
-          ...
+usage: cz [-h] [--debug] [-n NAME] [-nr NO_RAISE] {init,commit,c,ls,example,info,schema,bump,changelog,ch,check,version} ...
 
 Commitizen is a cli tool to generate conventional commits.
 For more information about the topic go to https://conventionalcommits.org/
@@ -136,9 +134,10 @@ For more information about the topic go to https://conventionalcommits.org/
 optional arguments:
   -h, --help            show this help message and exit
   --debug               use debug mode
-  -n NAME, --name NAME  use the given commitizen (default:
-                        cz_conventional_commits)
-  --version             get the version of the installed commitizen
+  -n NAME, --name NAME  use the given commitizen (default: cz_conventional_commits)
+  -nr NO_RAISE, --no-raise NO_RAISE
+                        comma separated error codes that won't rise error, e.g: cz -nr 1,2,3 bump. See codes at https://commitizen-
+                        tools.github.io/commitizen/exit_codes/
 
 commands:
   {init,commit,c,ls,example,info,schema,bump,changelog,ch,check,version}
@@ -149,12 +148,9 @@ commands:
     info                show information about the cz
     schema              show commit schema
     bump                bump semantic version based on the git log
-    changelog (ch)      generate changelog (note that it will overwrite
-                        existing file)
-    check               validates that a commit message matches the commitizen
-                        schema
-    version             get the version of the installed commitizen or the
-                        current project (default: installed commitizen)
+    changelog (ch)      generate changelog (note that it will overwrite existing file)
+    check               validates that a commit message matches the commitizen schema
+    version             get the version of the installed commitizen or the current project (default: installed commitizen)
 ```
 
 ## Setting up bash completion
diff --git a/docs/bump.md b/docs/bump.md
index 38e4f527..cbf7fef1 100644
--- a/docs/bump.md
+++ b/docs/bump.md
@@ -179,6 +179,69 @@ Example:
 cz bump --changelog --changelog-to-stdout > body.md
 ```
 
+## Avoid raising errors
+
+Some situations from commitizen rise an exit code different than 0.
+If the error code is different than 0, any CI or script running commitizen might be interrupted.
+
+If you have special use case, where you don't want one of this error codes to be raised, you can
+tell commitizen to not raise them.
+
+### Recommended use case
+
+At the moment, we've identified that the most common error code to skip is
+
+| Error name        | Exit code |
+| ----------------- | --------- |
+| NoneIncrementExit | 21        |
+
+There are some situations where you don't want to get an error code when some
+commits do not match your rules, you just want those commits to be skipped.
+
+```sh
+cz -nr 21 bump
+```
+
+### Easy way
+
+Check which error code was raised by commitizen by running in the terminal
+
+```sh
+echo $?
+```
+
+The output should be an integer like this
+
+```sh
+3
+```
+
+And then you can tell commitizen to ignore it:
+
+```sh
+cz --no-raise 3
+```
+
+You can tell commitizen to skip more than one if needed:
+
+```sh
+cz --no-raise 3,4,5
+```
+
+### Longer way
+
+Check the list of [exit_codes](./exit_codes.md) and understand which one you have
+to skip and why.
+
+Remember to document somewhere this, because you'll forget.
+
+For example if the system raises a `NoneIncrementExit` error, you look it up
+on the list and then you can use the exit code:
+
+```sh
+cz -nr 21 bump
+```
+
 ## Configuration
 
 ### `tag_format`
diff --git a/docs/exit_codes.md b/docs/exit_codes.md
index 28f8f641..f4c2fa82 100644
--- a/docs/exit_codes.md
+++ b/docs/exit_codes.md
@@ -4,26 +4,28 @@ Commitizen handles expected exceptions through `CommitizenException` and returns
 
 These exit codes can be found in `commitizen/exceptions.py::ExitCode`.
 
-| Exception | Exit Code | Description |
-| --------- | --------- | ----------- |
-| ExpectedExit | 0 | Expected exit |
-| DryRunExit | 0 | Exit due to passing `--dry-run` option |
-| NoCommitizenFoundException | 1 | Using a cz (e.g., `cz_jira`) that cannot be found in your system |
-| NotAGitProjectError | 2 | Not in a git project |
-| NoCommitsFoundError | 3 | No commit found |
-| NoVersionSpecifiedError | 4 | Version can not be found in configuration file |
-| NoPatternMapError | 5 | bump / changelog pattern or map can not be found in configuration file |
-| BumpCommitFailedError | 6 | Commit error when bumping version |
-| BumpTagFailedError | 7 | Tag error when bumping version |
-| NoAnswersError | 8 | No user response given |
-| CommitError | 9 | git commit error |
-| NoCommitBackupError | 10 | Commit back up file cannot be found |
-| NothingToCommitError | 11 | Nothing in staging to be committed |
-| CustomError | 12 | `CzException` raised |
-| NoCommandFoundError | 13 | No command found when running commitizen cli (e.g., `cz --debug`) |
-| InvalidCommitMessageError | 14 | The commit message does not pass `cz check` |
-| MissingConfigError | 15 | Configuration missed for `cz_customize` |
-| NoRevisionError | 16 | No revision found |
-| CurrentVersionNotFoundError | 17 | current version cannot be found in *version_files* |
-| InvalidCommandArgumentError | 18 | The argument provide to command is invalid (e.g. `cz check -commit-msg-file filename --rev-range master..`) |
-| InvalidConfigurationError | 19 | An error was found in the Commitizen Configuration, such as duplicates in `change_type_order` |
+| Exception                   | Exit Code | Description                                                                                                 |
+| --------------------------- | --------- | ----------------------------------------------------------------------------------------------------------- |
+| ExpectedExit                | 0         | Expected exit                                                                                               |
+| DryRunExit                  | 0         | Exit due to passing `--dry-run` option                                                                      |
+| NoCommitizenFoundException  | 1         | Using a cz (e.g., `cz_jira`) that cannot be found in your system                                            |
+| NotAGitProjectError         | 2         | Not in a git project                                                                                        |
+| NoCommitsFoundError         | 3         | No commit found                                                                                             |
+| NoVersionSpecifiedError     | 4         | Version can not be found in configuration file                                                              |
+| NoPatternMapError           | 5         | bump / changelog pattern or map can not be found in configuration file                                      |
+| BumpCommitFailedError       | 6         | Commit error when bumping version                                                                           |
+| BumpTagFailedError          | 7         | Tag error when bumping version                                                                              |
+| NoAnswersError              | 8         | No user response given                                                                                      |
+| CommitError                 | 9         | git commit error                                                                                            |
+| NoCommitBackupError         | 10        | Commit back up file cannot be found                                                                         |
+| NothingToCommitError        | 11        | Nothing in staging to be committed                                                                          |
+| CustomError                 | 12        | `CzException` raised                                                                                        |
+| NoCommandFoundError         | 13        | No command found when running commitizen cli (e.g., `cz --debug`)                                           |
+| InvalidCommitMessageError   | 14        | The commit message does not pass `cz check`                                                                 |
+| MissingConfigError          | 15        | Configuration missed for `cz_customize`                                                                     |
+| NoRevisionError             | 16        | No revision found                                                                                           |
+| CurrentVersionNotFoundError | 17        | current version cannot be found in _version_files_                                                          |
+| InvalidCommandArgumentError | 18        | The argument provide to command is invalid (e.g. `cz check -commit-msg-file filename --rev-range master..`) |
+| InvalidConfigurationError   | 19        | An error was found in the Commitizen Configuration, such as duplicates in `change_type_order`               |
+| NotAllowed                  | 20        | `--incremental` cannot be combined with a `rev_range`                                                       |
+| NoneIncrementExit           | 21        | The commits found are not elegible to be bumped                                                             |
diff --git a/docs/tutorials/github_actions.md b/docs/tutorials/github_actions.md
index 58726afc..b5d05bb9 100644
--- a/docs/tutorials/github_actions.md
+++ b/docs/tutorials/github_actions.md
@@ -12,7 +12,7 @@ the new tag, back to your master branch, we have to:
    with the following content.
 
 !!! warning
-If you use `GITHUB_TOKEN` instead of `PERSONAL_ACCESS_TOKEN`, the job won't trigger another workflow. It's like using `[skip ci]` in other CI's.
+    If you use `GITHUB_TOKEN` instead of `PERSONAL_ACCESS_TOKEN`, the job won't trigger another workflow. It's like using `[skip ci]` in other CI's.
 
 ```yaml
 name: Bump version


Test patch:
diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py
index b5c7b6b1..1d1df889 100644
--- a/tests/commands/test_bump_command.py
+++ b/tests/commands/test_bump_command.py
@@ -358,7 +358,7 @@ def test_none_increment_should_not_call_git_tag_and_error_code_is_not_zero(
             cli.main()
         except NoneIncrementExit as e:
             git.tag.assert_not_called()
-            assert e.exit_code == ExitCode.NO_COMMITS_FOUND
+            assert e.exit_code == ExitCode.NO_INCREMENT
             raise e
 
     # restore pop stashed
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 44f182b0..383d7a18 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -95,3 +95,46 @@ def test_argcomplete_activation():
     output = subprocess.run(["register-python-argcomplete", "cz"])
 
     assert output.returncode == 0
+
+
+def test_commitizen_excepthook_no_raises(capsys):
+    with pytest.raises(SystemExit) as excinfo:
+        cli.commitizen_excepthook(
+            NotAGitProjectError,
+            NotAGitProjectError(),
+            "",
+            no_raise=[NotAGitProjectError.exit_code],
+        )
+
+    assert excinfo.type == SystemExit
+    assert excinfo.value.code == 0
+
+
+def test_parse_no_raise_single_integer():
+    input_str = "1"
+    result = cli.parse_no_raise(input_str)
+    assert result == [1]
+
+
+def test_parse_no_raise_integers():
+    input_str = "1,2,3"
+    result = cli.parse_no_raise(input_str)
+    assert result == [1, 2, 3]
+
+
+def test_parse_no_raise_error_code():
+    input_str = "NO_COMMITIZEN_FOUND,NO_COMMITS_FOUND,NO_PATTERN_MAP"
+    result = cli.parse_no_raise(input_str)
+    assert result == [1, 3, 5]
+
+
+def test_parse_no_raise_mix_integer_error_code():
+    input_str = "NO_COMMITIZEN_FOUND,2,NO_COMMITS_FOUND,4"
+    result = cli.parse_no_raise(input_str)
+    assert result == [1, 2, 3, 4]
+
+
+def test_parse_no_raise_mix_invalid_arg_is_skipped():
+    input_str = "NO_COMMITIZEN_FOUND,2,nothing,4"
+    result = cli.parse_no_raise(input_str)
+    assert result == [1, 2, 4]