Skip to content

Commit 36d184a

Browse files
committed
feat(bump): added support for running arbitrary hooks during bump
To make commitizen integrate even better, new configuration keys hooks_pre_bump and hooks_post_bump were added to allow to run arbitrary commands prior to and right after running bump Closes: #292
1 parent 3c7d67d commit 36d184a

File tree

6 files changed

+134
-1
lines changed

6 files changed

+134
-1
lines changed

commitizen/commands/bump.py

Lines changed: 31 additions & 1 deletion
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.hooks_pre_bump = self.config.settings["hooks_pre_bump"]
62+
self.hooks_post_bump = self.config.settings["hooks_post_bump"]
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.hooks_pre_bump:
278+
hooks.run(
279+
self.hooks_pre_bump,
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.hooks_post_bump:
320+
hooks.run(
321+
self.hooks_post_bump,
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

Lines changed: 4 additions & 0 deletions
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+
hooks_pre_bump: Optional[List[str]]
44+
hooks_post_bump: 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+
"hooks_pre_bump": [],
71+
"hooks_post_bump": [],
6872
}
6973

7074
MAJOR = "MAJOR"

commitizen/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class ExitCode(enum.IntEnum):
2929
UNRECOGNIZED_CHARACTERSET_ENCODING = 22
3030
GIT_COMMAND_ERROR = 23
3131
INVALID_MANUAL_VERSION = 24
32+
RUN_HOOK_FAILED = 25
3233

3334

3435
class CommitizenException(Exception):
@@ -163,3 +164,7 @@ class GitCommandError(CommitizenException):
163164

164165
class InvalidManualVersion(CommitizenException):
165166
exit_code = ExitCode.INVALID_MANUAL_VERSION
167+
168+
169+
class RunHookError(CommitizenException):
170+
exit_code = ExitCode.RUN_HOOK_FAILED

commitizen/hooks.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import os
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 name, value in env.items():
12+
name = _env_prefix + name.upper()
13+
value = str(value) if value is not None else ""
14+
os.environ[name] = value
15+
16+
for hook in hooks:
17+
out.info(f"Running hook '{hook}'")
18+
19+
c = cmd.run(hook)
20+
21+
if c.out:
22+
out.write(c.out)
23+
if c.err:
24+
out.error(c.err)
25+
26+
if c.return_code != 0:
27+
raise RunHookError(f"Running hook '{hook}' failed")

docs/bump.md

Lines changed: 55 additions & 0 deletions
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+
### `hooks_pre_bump`
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+
hooks_pre_bump = [
438+
"scripts/generate_documentation.sh"
439+
]
440+
```
441+
442+
---
443+
444+
### `hooks_post_bump`
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+
hooks_post_bump = [
465+
"scripts/slack_notification.sh"
466+
]
467+
```
468+
414469
## Custom bump
415470
416471
Read the [customizing section](./customization.md).

tests/test_conf.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
["pointer", "reverse"],
2020
["question", "underline"]
2121
]
22+
hooks_pre_bump = [
23+
"scripts/generate_documentation.sh"
24+
]
25+
hooks_post_bump = ["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+
"hooks_pre_bump": ["scripts/generate_documentation.sh"],
39+
"hooks_post_bump": ["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+
"hooks_pre_bump": ["scripts/generate_documentation.sh"],
59+
"hooks_post_bump": ["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+
"hooks_pre_bump": ["scripts/generate_documentation.sh"],
77+
"hooks_post_bump": ["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+
"hooks_pre_bump": ["scripts/generate_documentation.sh"],
87+
"hooks_post_bump": ["scripts/slack_notification.sh"],
7688
}
7789

7890

0 commit comments

Comments
 (0)