Skip to content

Commit 27616c4

Browse files
skoefLee-W
authored andcommitted
feat(bump): added support for running arbitrary hooks during bump
To make commitizen integrate even better, new configuration keys pre_bump_hooks and post_bump_hooks were added to allow to run arbitrary commands prior to and right after running bump Closes: #292
1 parent 52057ac commit 27616c4

File tree

9 files changed

+151
-3
lines changed

9 files changed

+151
-3
lines changed

.github/workflows/pythonpackage.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
python-check:
77
strategy:
88
matrix:
9-
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
9+
python-version: ["3.7", "3.8", "3.9", "3.10"]
1010
platform: [ubuntu-20.04, macos-latest, windows-latest]
1111
runs-on: ${{ matrix.platform }}
1212
steps:

commitizen/cmd.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ def _try_decode(bytes_: bytes) -> str:
2727
raise CharacterSetDecodeError() from e
2828

2929

30-
def run(cmd: str) -> Command:
30+
def run(cmd: str, env=None) -> Command:
3131
process = subprocess.Popen(
3232
cmd,
3333
shell=True,
3434
stdout=subprocess.PIPE,
3535
stderr=subprocess.PIPE,
3636
stdin=subprocess.PIPE,
37+
env=env,
3738
)
3839
stdout, stderr = process.communicate()
3940
return_code = process.returncode

commitizen/commands/bump.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import questionary
66
from packaging.version import InvalidVersion, Version
77

8-
from commitizen import bump, cmd, defaults, factory, git, out
8+
from commitizen import bump, cmd, defaults, factory, git, hooks, out
99
from commitizen.commands.changelog import Changelog
1010
from commitizen.config import BaseConfig
1111
from commitizen.exceptions import (
@@ -58,6 +58,8 @@ def __init__(self, config: BaseConfig, arguments: dict):
5858
self.no_verify = arguments["no_verify"]
5959
self.check_consistency = arguments["check_consistency"]
6060
self.retry = arguments["retry"]
61+
self.pre_bump_hooks = self.config.settings["pre_bump_hooks"]
62+
self.post_bump_hooks = self.config.settings["post_bump_hooks"]
6163

6264
def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool:
6365
"""Check if reading the whole git tree up to HEAD is needed."""
@@ -272,6 +274,20 @@ def __call__(self): # noqa: C901
272274

273275
self.config.set_key("version", str(new_version))
274276

277+
if self.pre_bump_hooks:
278+
hooks.run(
279+
self.pre_bump_hooks,
280+
_env_prefix="CZ_PRE_",
281+
is_initial=is_initial,
282+
current_version=current_version,
283+
current_tag_version=current_tag_version,
284+
new_version=new_version.public,
285+
new_tag_version=new_tag_version,
286+
message=message,
287+
increment=increment,
288+
changelog_file_name=changelog_cmd.file_name if self.changelog else None,
289+
)
290+
275291
if is_files_only:
276292
raise ExpectedExit()
277293

@@ -300,6 +316,20 @@ def __call__(self): # noqa: C901
300316
if c.return_code != 0:
301317
raise BumpTagFailedError(c.err)
302318

319+
if self.post_bump_hooks:
320+
hooks.run(
321+
self.post_bump_hooks,
322+
_env_prefix="CZ_POST_",
323+
was_initial=is_initial,
324+
previous_version=current_version,
325+
previous_tag_version=current_tag_version,
326+
current_version=new_version.public,
327+
current_tag_version=new_tag_version,
328+
message=message,
329+
increment=increment,
330+
changelog_file_name=changelog_cmd.file_name if self.changelog else None,
331+
)
332+
303333
# TODO: For v3 output this only as diagnostic and remove this if
304334
if self.changelog_to_stdout:
305335
out.diagnostic("Done!")

commitizen/defaults.py

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class Settings(TypedDict, total=False):
4040
style: Optional[List[Tuple[str, str]]]
4141
customize: CzSettings
4242
major_version_zero: bool
43+
pre_bump_hooks: Optional[List[str]]
44+
post_bump_hooks: Optional[List[str]]
4345

4446

4547
name: str = "cz_conventional_commits"
@@ -65,6 +67,8 @@ class Settings(TypedDict, total=False):
6567
"update_changelog_on_bump": False,
6668
"use_shortcuts": False,
6769
"major_version_zero": False,
70+
"pre_bump_hooks": [],
71+
"post_bump_hooks": [],
6872
}
6973

7074
MAJOR = "MAJOR"

commitizen/exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class ExitCode(enum.IntEnum):
3030
GIT_COMMAND_ERROR = 23
3131
INVALID_MANUAL_VERSION = 24
3232
INIT_FAILED = 25
33+
RUN_HOOK_FAILED = 26
3334

3435

3536
class CommitizenException(Exception):
@@ -168,3 +169,7 @@ class InvalidManualVersion(CommitizenException):
168169

169170
class InitFailedError(CommitizenException):
170171
exit_code = ExitCode.INIT_FAILED
172+
173+
174+
class RunHookError(CommitizenException):
175+
exit_code = ExitCode.RUN_HOOK_FAILED

commitizen/hooks.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from __future__ import annotations
2+
3+
from commitizen import cmd, out
4+
from commitizen.exceptions import RunHookError
5+
6+
7+
def run(hooks, _env_prefix="CZ_", **env):
8+
if isinstance(hooks, str):
9+
hooks = [hooks]
10+
11+
for hook in hooks:
12+
out.info(f"Running hook '{hook}'")
13+
14+
c = cmd.run(hook, env=_format_env(_env_prefix, env))
15+
16+
if c.out:
17+
out.write(c.out)
18+
if c.err:
19+
out.error(c.err)
20+
21+
if c.return_code != 0:
22+
raise RunHookError(f"Running hook '{hook}' failed")
23+
24+
25+
def _format_env(prefix: str, env: dict[str, str]) -> dict[str, str]:
26+
"""_format_env() prefixes all given environment variables with the given
27+
prefix so it can be passed directly to cmd.run()."""
28+
penv = dict()
29+
for name, value in env.items():
30+
name = prefix + name.upper()
31+
value = str(value) if value is not None else ""
32+
penv[name] = value
33+
34+
return penv

docs/bump.md

+55
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,61 @@ Defaults to: `false`
411411
major_version_zero = true
412412
```
413413
414+
---
415+
416+
### `pre_bump_hooks`
417+
418+
A list of optional commands that will run right _after_ updating `version_files`
419+
and _before_ actual committing and tagging the release.
420+
421+
Useful when you need to generate documentation based on the new version. During
422+
execution of the script, some environment variables are available:
423+
424+
| Variable | Description |
425+
| ---------------------------- | ---------------------------------------------------------- |
426+
| `CZ_PRE_IS_INITIAL` | `True` when this is the initial release, `False` otherwise |
427+
| `CZ_PRE_CURRENT_VERSION` | Current version, before the bump |
428+
| `CZ_PRE_CURRENT_TAG_VERSION` | Current version tag, before the bump |
429+
| `CZ_PRE_NEW_VERSION` | New version, after the bump |
430+
| `CZ_PRE_NEW_TAG_VERSION` | New version tag, after the bump |
431+
| `CZ_PRE_MESSAGE` | Commit message of the bump |
432+
| `CZ_PRE_INCREMENT` | Whether this is a `MAJOR`, `MINOR` or `PATH` release |
433+
| `CZ_PRE_CHANGELOG_FILE_NAME` | Path to the changelog file, if available |
434+
435+
```toml
436+
[tool.commitizen]
437+
pre_bump_hooks = [
438+
"scripts/generate_documentation.sh"
439+
]
440+
```
441+
442+
---
443+
444+
### `post_bump_hooks`
445+
446+
A list of optional commands that will run right _after_ committing and tagging the release.
447+
448+
Useful when you need to send notifications about a release, or further automate deploying the
449+
release. During execution of the script, some environment variables are available:
450+
451+
| Variable | Description |
452+
| ------------------------------ | ----------------------------------------------------------- |
453+
| `CZ_POST_WAS_INITIAL` | `True` when this was the initial release, `False` otherwise |
454+
| `CZ_POST_PREVIOUS_VERSION` | Previous version, before the bump |
455+
| `CZ_POST_PREVIOUS_TAG_VERSION` | Previous version tag, before the bump |
456+
| `CZ_POST_CURRENT_VERSION` | Current version, after the bump |
457+
| `CZ_POST_CURRENT_TAG_VERSION` | Current version tag, after the bump |
458+
| `CZ_POST_MESSAGE` | Commit message of the bump |
459+
| `CZ_POST_INCREMENT` | Whether this wass a `MAJOR`, `MINOR` or `PATH` release |
460+
| `CZ_POST_CHANGELOG_FILE_NAME` | Path to the changelog file, if available |
461+
462+
```toml
463+
[tool.commitizen]
464+
post_bump_hooks = [
465+
"scripts/slack_notification.sh"
466+
]
467+
```
468+
414469
## Custom bump
415470
416471
Read the [customizing section](./customization.md).

tests/test_bump_hooks.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from commitizen import hooks
2+
3+
4+
def test_format_env():
5+
result = hooks._format_env("TEST_", {"foo": "bar", "bar": "baz"})
6+
assert "TEST_FOO" in result and result["TEST_FOO"] == "bar"
7+
assert "TEST_BAR" in result and result["TEST_BAR"] == "baz"

tests/test_conf.py

+12
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
["pointer", "reverse"],
2020
["question", "underline"]
2121
]
22+
pre_bump_hooks = [
23+
"scripts/generate_documentation.sh"
24+
]
25+
post_bump_hooks = ["scripts/slack_notification.sh"]
2226
2327
[tool.black]
2428
line-length = 88
@@ -31,6 +35,8 @@
3135
"version": "1.0.0",
3236
"version_files": ["commitizen/__version__.py", "pyproject.toml"],
3337
"style": [["pointer", "reverse"], ["question", "underline"]],
38+
"pre_bump_hooks": ["scripts/generate_documentation.sh"],
39+
"post_bump_hooks": ["scripts/slack_notification.sh"],
3440
}
3541
}
3642

@@ -49,6 +55,8 @@
4955
"update_changelog_on_bump": False,
5056
"use_shortcuts": False,
5157
"major_version_zero": False,
58+
"pre_bump_hooks": ["scripts/generate_documentation.sh"],
59+
"post_bump_hooks": ["scripts/slack_notification.sh"],
5260
}
5361

5462
_new_settings = {
@@ -65,6 +73,8 @@
6573
"update_changelog_on_bump": False,
6674
"use_shortcuts": False,
6775
"major_version_zero": False,
76+
"pre_bump_hooks": ["scripts/generate_documentation.sh"],
77+
"post_bump_hooks": ["scripts/slack_notification.sh"],
6878
}
6979

7080
_read_settings = {
@@ -73,6 +83,8 @@
7383
"version_files": ["commitizen/__version__.py", "pyproject.toml"],
7484
"style": [["pointer", "reverse"], ["question", "underline"]],
7585
"changelog_file": "CHANGELOG.md",
86+
"pre_bump_hooks": ["scripts/generate_documentation.sh"],
87+
"post_bump_hooks": ["scripts/slack_notification.sh"],
7688
}
7789

7890

0 commit comments

Comments
 (0)