mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-04-02 02:56:52 +00:00
Add prepare-commit-msg hook
Added new hook for templating prepare-commit-msg hook. Added 2 templates to use keepgin `prepare_commit_msg_append.j2`as default. The idea is that developers could create their own templates accordingly to teams commit conventions and override them in the hook arguments when configuring it.
This commit is contained in:
parent
18c2496e4c
commit
e01e1717b1
8 changed files with 236 additions and 0 deletions
|
|
@ -174,6 +174,12 @@
|
|||
language: python
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
- id: prepare-commit-msg
|
||||
name: prepare commit message
|
||||
description: Formatter Helper for your commit message
|
||||
entry: prepare-commit-msg
|
||||
language: python
|
||||
stages: [prepare-commit-msg]
|
||||
- id: requirements-txt-fixer
|
||||
name: fix requirements.txt
|
||||
description: sorts entries in requirements.txt.
|
||||
|
|
|
|||
123
pre_commit_hooks/prepare_commit_msg.py
Normal file
123
pre_commit_hooks/prepare_commit_msg.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from typing import Sequence
|
||||
|
||||
from jinja2 import Environment
|
||||
from jinja2 import PackageLoader
|
||||
from jinja2 import select_autoescape
|
||||
|
||||
from pre_commit_hooks.util import CalledProcessError
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
|
||||
|
||||
def get_current_branch() -> str:
|
||||
try:
|
||||
ref_name = cmd_output('git', 'symbolic-ref', '--short', 'HEAD')
|
||||
except CalledProcessError:
|
||||
return ''
|
||||
|
||||
return ref_name.strip()
|
||||
|
||||
|
||||
def _configure_args(
|
||||
parser: argparse.ArgumentParser,
|
||||
) -> argparse.ArgumentParser:
|
||||
parser.add_argument(
|
||||
'-t', '--template', default='prepare_commit_msg_append.j2',
|
||||
help='Template to use for the commit message.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-b', '--branch', action='append', default=['main', 'master'],
|
||||
help='Branch to skip, may be specified multiple times.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--pattern', action='append', default=['(?<=feature/).*'],
|
||||
help='RegEx Pattern for recognising Ticket Numbers in branch, '
|
||||
'may be specified multiple times.',
|
||||
)
|
||||
parser.add_argument('COMMIT_MSG_FILE', nargs=argparse.REMAINDER)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def get_jinja_env() -> Environment:
|
||||
return Environment(
|
||||
loader=PackageLoader('pre_commit_hooks'),
|
||||
autoescape=select_autoescape(),
|
||||
)
|
||||
|
||||
|
||||
def get_rendered_template(
|
||||
jinja: Environment,
|
||||
template_file: str,
|
||||
variables: dict[str, str],
|
||||
) -> str:
|
||||
template = jinja.get_template(template_file)
|
||||
return template.render(variables)
|
||||
|
||||
|
||||
def update_commit_file(
|
||||
jinja: Environment,
|
||||
commit_msg_file: str,
|
||||
template: str,
|
||||
ticket: str,
|
||||
) -> int:
|
||||
try:
|
||||
with open(commit_msg_file) as f:
|
||||
data = f.readlines()
|
||||
|
||||
data_as_str = ''.join([item for item in data])
|
||||
# if message already contain ticket number means
|
||||
# it is under git commit --amend or rebase or alike
|
||||
# where message was already set in the past
|
||||
if ticket in data_as_str:
|
||||
return 0
|
||||
|
||||
variables = {
|
||||
'ticket': ticket,
|
||||
'content': data_as_str,
|
||||
}
|
||||
|
||||
content = get_rendered_template(
|
||||
jinja=jinja,
|
||||
template_file=template,
|
||||
variables=variables,
|
||||
)
|
||||
with open(commit_msg_file, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
return 0
|
||||
except OSError as err:
|
||||
print(f'OS error: {err}')
|
||||
return 1
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = _configure_args(argparse.ArgumentParser())
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
current = get_current_branch()
|
||||
branches = frozenset(args.branch)
|
||||
if current in branches:
|
||||
# checked black listed branches
|
||||
return 0
|
||||
|
||||
patterns = frozenset(args.pattern)
|
||||
matches = [
|
||||
match.group(0)
|
||||
for match in (re.search(pattern, current) for pattern in patterns)
|
||||
if match
|
||||
]
|
||||
if len(matches) == 0:
|
||||
# checked white listed branches
|
||||
return 0
|
||||
|
||||
jinja = get_jinja_env()
|
||||
commit_file = args.COMMIT_MSG_FILE[0]
|
||||
return update_commit_file(jinja, commit_file, args.template, matches[0])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
3
pre_commit_hooks/templates/prepare_commit_msg_append.j2
Normal file
3
pre_commit_hooks/templates/prepare_commit_msg_append.j2
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{{ content }}
|
||||
|
||||
Relates: {{ ticket }}
|
||||
1
pre_commit_hooks/templates/prepare_commit_msg_prepend.j2
Normal file
1
pre_commit_hooks/templates/prepare_commit_msg_prepend.j2
Normal file
|
|
@ -0,0 +1 @@
|
|||
[{{ ticket }}] {{ content }}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
covdefaults
|
||||
coverage
|
||||
Jinja2
|
||||
pytest
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ classifiers =
|
|||
[options]
|
||||
packages = find:
|
||||
install_requires =
|
||||
Jinja2>=3.1.2
|
||||
ruamel.yaml>=0.15
|
||||
tomli>=1.1.0;python_version<"3.11"
|
||||
python_requires = >=3.7
|
||||
|
|
@ -62,6 +63,7 @@ console_scripts =
|
|||
mixed-line-ending = pre_commit_hooks.mixed_line_ending:main
|
||||
name-tests-test = pre_commit_hooks.tests_should_end_in_test:main
|
||||
no-commit-to-branch = pre_commit_hooks.no_commit_to_branch:main
|
||||
prepare-commit-msg = pre_commit_hooks.prepare_commit_msg:main
|
||||
pre-commit-hooks-removed = pre_commit_hooks.removed:main
|
||||
pretty-format-json = pre_commit_hooks.pretty_format_json:main
|
||||
requirements-txt-fixer = pre_commit_hooks.requirements_txt_fixer:main
|
||||
|
|
|
|||
|
|
@ -14,3 +14,8 @@ def get_resource_path(path):
|
|||
def git_commit(*args, **kwargs):
|
||||
cmd = ('git', 'commit', '--no-gpg-sign', '--no-verify', '--no-edit', *args)
|
||||
subprocess.check_call(cmd, **kwargs)
|
||||
|
||||
|
||||
def get_template_path(path):
|
||||
parent_dir = os.path.abspath(os.path.join(TESTING_DIR, os.pardir))
|
||||
return os.path.join(parent_dir, 'templates', path)
|
||||
|
|
|
|||
95
tests/prepare_commit_msg_test.py
Normal file
95
tests/prepare_commit_msg_test.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.prepare_commit_msg import get_current_branch
|
||||
from pre_commit_hooks.prepare_commit_msg import get_jinja_env
|
||||
from pre_commit_hooks.prepare_commit_msg import main
|
||||
from pre_commit_hooks.prepare_commit_msg import update_commit_file
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
|
||||
|
||||
def test_current_branch(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', 'feature')
|
||||
assert get_current_branch() == 'feature'
|
||||
|
||||
cmd_output('git', 'checkout', '-b', 'feature/branch')
|
||||
assert get_current_branch() == 'feature/branch'
|
||||
|
||||
|
||||
# Input, expected value, branch, template
|
||||
TESTS = (
|
||||
(
|
||||
b'',
|
||||
b'[TT-01] ',
|
||||
'feature/TT-01',
|
||||
'prepare_commit_msg_prepend.j2',
|
||||
),
|
||||
(
|
||||
b'[TT-02] Some message',
|
||||
b'[TT-02] Some message',
|
||||
'feature/TT-02',
|
||||
'prepare_commit_msg_prepend.j2',
|
||||
),
|
||||
(
|
||||
b'Initial message',
|
||||
b'[TT-03] Initial message',
|
||||
'feature/TT-03',
|
||||
'prepare_commit_msg_prepend.j2',
|
||||
),
|
||||
(
|
||||
b'',
|
||||
b'\n\nRelates: AA-01',
|
||||
'feature/AA-01',
|
||||
'prepare_commit_msg_append.j2',
|
||||
),
|
||||
(
|
||||
b'Initial message',
|
||||
b'Initial message\n\nRelates: AA-02',
|
||||
'feature/AA-02',
|
||||
'prepare_commit_msg_append.j2',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_s', 'expected_val', 'branch_name', 'template'),
|
||||
TESTS,
|
||||
)
|
||||
def test_update_commit_file(
|
||||
input_s, expected_val, branch_name, template,
|
||||
temp_git_dir,
|
||||
):
|
||||
with temp_git_dir.as_cwd():
|
||||
path = temp_git_dir.join('COMMIT_EDITMSG')
|
||||
path.write_binary(input_s)
|
||||
ticket = branch_name.split('/')[1]
|
||||
jinja = get_jinja_env()
|
||||
update_commit_file(jinja, path, template, ticket)
|
||||
|
||||
assert path.read_binary() == expected_val
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_s', 'expected_val', 'branch_name', 'template'),
|
||||
TESTS,
|
||||
)
|
||||
def test_main(
|
||||
input_s, expected_val, branch_name, template,
|
||||
temp_git_dir,
|
||||
):
|
||||
with temp_git_dir.as_cwd():
|
||||
path = temp_git_dir.join('file.txt')
|
||||
path.write_binary(input_s)
|
||||
assert path.read_binary() == input_s
|
||||
|
||||
cmd_output('git', 'checkout', '-b', branch_name)
|
||||
assert main(
|
||||
argv=[
|
||||
'-t', template,
|
||||
'-p', '(?<=feature/).*',
|
||||
str(path),
|
||||
],
|
||||
) == 0
|
||||
assert path.read_binary() == expected_val
|
||||
Loading…
Add table
Add a link
Reference in a new issue