mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-02-17 08:14:42 +04:00
Adds support for prepare-commit-msg hooks
Adds a prepare-commit-msg hook stage which allows for hooks which add dynamic suggested/placeholder text to commit messages that an author can use as a starting point for writing a commit message
This commit is contained in:
parent
809b7482df
commit
e60f541559
7 changed files with 142 additions and 6 deletions
|
|
@ -190,7 +190,7 @@ def _compute_cols(hooks, verbose):
|
||||||
def _all_filenames(args):
|
def _all_filenames(args):
|
||||||
if args.origin and args.source:
|
if args.origin and args.source:
|
||||||
return git.get_changed_files(args.origin, args.source)
|
return git.get_changed_files(args.origin, args.source)
|
||||||
elif args.hook_stage == 'commit-msg':
|
elif args.hook_stage in ['prepare-commit-msg', 'commit-msg']:
|
||||||
return (args.commit_msg_filename,)
|
return (args.commit_msg_filename,)
|
||||||
elif args.files:
|
elif args.files:
|
||||||
return args.files
|
return args.files
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,6 @@ LOCAL_REPO_VERSION = '1'
|
||||||
VERSION = importlib_metadata.version('pre_commit')
|
VERSION = importlib_metadata.version('pre_commit')
|
||||||
|
|
||||||
# `manual` is not invoked by any installed git hook. See #719
|
# `manual` is not invoked by any installed git hook. See #719
|
||||||
STAGES = ('commit', 'commit-msg', 'manual', 'push')
|
STAGES = ('commit', 'prepare-commit-msg', 'commit-msg', 'manual', 'push')
|
||||||
|
|
||||||
DEFAULT = 'default'
|
DEFAULT = 'default'
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,9 @@ def _add_config_option(parser):
|
||||||
|
|
||||||
def _add_hook_type_option(parser):
|
def _add_hook_type_option(parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t', '--hook-type', choices=('pre-commit', 'pre-push', 'commit-msg'),
|
'-t', '--hook-type', choices=(
|
||||||
|
'pre-commit', 'pre-push', 'prepare-commit-msg', 'commit-msg',
|
||||||
|
),
|
||||||
default='pre-commit',
|
default='pre-commit',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ def _pre_push(stdin):
|
||||||
|
|
||||||
def _opts(stdin):
|
def _opts(stdin):
|
||||||
fns = {
|
fns = {
|
||||||
|
'prepare-commit-msg': lambda _: ('--commit-msg-filename', sys.argv[1]),
|
||||||
'commit-msg': lambda _: ('--commit-msg-filename', sys.argv[1]),
|
'commit-msg': lambda _: ('--commit-msg-filename', sys.argv[1]),
|
||||||
'pre-commit': lambda _: (),
|
'pre-commit': lambda _: (),
|
||||||
'pre-push': _pre_push,
|
'pre-push': _pre_push,
|
||||||
|
|
|
||||||
|
|
@ -655,7 +655,65 @@ def test_commit_msg_legacy(commit_msg_repo, tempdir_factory, store):
|
||||||
assert second_line.startswith('Must have "Signed off by:"...')
|
assert second_line.startswith('Must have "Signed off by:"...')
|
||||||
|
|
||||||
|
|
||||||
def test_install_disallow_mising_config(tempdir_factory, store):
|
def test_prepare_commit_msg_integration_failing(
|
||||||
|
failing_prepare_commit_msg_repo, tempdir_factory, store,
|
||||||
|
):
|
||||||
|
install(C.CONFIG_FILE, store, hook_type='prepare-commit-msg')
|
||||||
|
retc, out = _get_commit_output(tempdir_factory)
|
||||||
|
assert retc == 1
|
||||||
|
assert out.startswith('Add "Signed off by:"...')
|
||||||
|
assert out.strip().endswith('...Failed')
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_commit_msg_integration_passing(
|
||||||
|
prepare_commit_msg_repo, tempdir_factory, store,
|
||||||
|
):
|
||||||
|
install(C.CONFIG_FILE, store, hook_type='prepare-commit-msg')
|
||||||
|
msg = 'Hi'
|
||||||
|
retc, out = _get_commit_output(tempdir_factory, msg=msg)
|
||||||
|
assert retc == 0
|
||||||
|
first_line = out.splitlines()[0]
|
||||||
|
assert first_line.startswith('Add "Signed off by:"...')
|
||||||
|
assert first_line.endswith('...Passed')
|
||||||
|
commit_msg_path = os.path.join(
|
||||||
|
prepare_commit_msg_repo, '.git/COMMIT_EDITMSG',
|
||||||
|
)
|
||||||
|
with io.open(commit_msg_path, 'rt') as f:
|
||||||
|
assert 'Signed off by: ' in f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_commit_msg_legacy(
|
||||||
|
prepare_commit_msg_repo, tempdir_factory, store,
|
||||||
|
):
|
||||||
|
hook_path = os.path.join(
|
||||||
|
prepare_commit_msg_repo, '.git/hooks/prepare-commit-msg',
|
||||||
|
)
|
||||||
|
mkdirp(os.path.dirname(hook_path))
|
||||||
|
with io.open(hook_path, 'w') as hook_file:
|
||||||
|
hook_file.write(
|
||||||
|
'#!/usr/bin/env bash\n'
|
||||||
|
'set -eu\n'
|
||||||
|
'test -e "$1"\n'
|
||||||
|
'echo legacy\n',
|
||||||
|
)
|
||||||
|
make_executable(hook_path)
|
||||||
|
|
||||||
|
install(C.CONFIG_FILE, store, hook_type='prepare-commit-msg')
|
||||||
|
|
||||||
|
msg = 'Hi'
|
||||||
|
retc, out = _get_commit_output(tempdir_factory, msg=msg)
|
||||||
|
assert retc == 0
|
||||||
|
first_line, second_line = out.splitlines()[:2]
|
||||||
|
assert first_line == 'legacy'
|
||||||
|
assert second_line.startswith('Add "Signed off by:"...')
|
||||||
|
commit_msg_path = os.path.join(
|
||||||
|
prepare_commit_msg_repo, '.git/COMMIT_EDITMSG',
|
||||||
|
)
|
||||||
|
with io.open(commit_msg_path, 'rt') as f:
|
||||||
|
assert 'Signed off by: ' in f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def test_install_disallow_missing_config(tempdir_factory, store):
|
||||||
path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
|
path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
|
||||||
with cwd(path):
|
with cwd(path):
|
||||||
remove_config_from_repo(path)
|
remove_config_from_repo(path)
|
||||||
|
|
@ -668,7 +726,7 @@ def test_install_disallow_mising_config(tempdir_factory, store):
|
||||||
assert ret == 1
|
assert ret == 1
|
||||||
|
|
||||||
|
|
||||||
def test_install_allow_mising_config(tempdir_factory, store):
|
def test_install_allow_missing_config(tempdir_factory, store):
|
||||||
path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
|
path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
|
||||||
with cwd(path):
|
with cwd(path):
|
||||||
remove_config_from_repo(path)
|
remove_config_from_repo(path)
|
||||||
|
|
|
||||||
|
|
@ -557,7 +557,12 @@ def test_stages(cap_out, store, repo_with_passing_hook):
|
||||||
'language': 'pygrep',
|
'language': 'pygrep',
|
||||||
'stages': [stage],
|
'stages': [stage],
|
||||||
}
|
}
|
||||||
for i, stage in enumerate(('commit', 'push', 'manual'), 1)
|
for i, stage in enumerate(
|
||||||
|
(
|
||||||
|
'commit', 'push', 'manual', 'prepare-commit-msg',
|
||||||
|
'commit-msg',
|
||||||
|
), 1,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
add_config_to_repo(repo_with_passing_hook, config)
|
add_config_to_repo(repo_with_passing_hook, config)
|
||||||
|
|
@ -575,6 +580,8 @@ def test_stages(cap_out, store, repo_with_passing_hook):
|
||||||
assert _run_for_stage('commit').startswith(b'hook 1...')
|
assert _run_for_stage('commit').startswith(b'hook 1...')
|
||||||
assert _run_for_stage('push').startswith(b'hook 2...')
|
assert _run_for_stage('push').startswith(b'hook 2...')
|
||||||
assert _run_for_stage('manual').startswith(b'hook 3...')
|
assert _run_for_stage('manual').startswith(b'hook 3...')
|
||||||
|
assert _run_for_stage('prepare-commit-msg').startswith(b'hook 4...')
|
||||||
|
assert _run_for_stage('commit-msg').startswith(b'hook 5...')
|
||||||
|
|
||||||
|
|
||||||
def test_commit_msg_hook(cap_out, store, commit_msg_repo):
|
def test_commit_msg_hook(cap_out, store, commit_msg_repo):
|
||||||
|
|
@ -593,6 +600,25 @@ def test_commit_msg_hook(cap_out, store, commit_msg_repo):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_commit_msg_hook(cap_out, store, prepare_commit_msg_repo):
|
||||||
|
filename = '.git/COMMIT_EDITMSG'
|
||||||
|
with io.open(filename, 'w') as f:
|
||||||
|
f.write('This is the commit message')
|
||||||
|
|
||||||
|
_test_run(
|
||||||
|
cap_out,
|
||||||
|
store,
|
||||||
|
prepare_commit_msg_repo,
|
||||||
|
{'hook_stage': 'prepare-commit-msg', 'commit_msg_filename': filename},
|
||||||
|
expected_outputs=[b'Add "Signed off by:"', b'Passed'],
|
||||||
|
expected_ret=0,
|
||||||
|
stage=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
with io.open(filename, 'rt') as f:
|
||||||
|
assert 'Signed off by: ' in f.read()
|
||||||
|
|
||||||
|
|
||||||
def test_local_hook_passes(cap_out, store, repo_with_passing_hook):
|
def test_local_hook_passes(cap_out, store, repo_with_passing_hook):
|
||||||
config = {
|
config = {
|
||||||
'repo': 'local',
|
'repo': 'local',
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from pre_commit import output
|
||||||
from pre_commit.logging_handler import logging_handler
|
from pre_commit.logging_handler import logging_handler
|
||||||
from pre_commit.store import Store
|
from pre_commit.store import Store
|
||||||
from pre_commit.util import cmd_output
|
from pre_commit.util import cmd_output
|
||||||
|
from pre_commit.util import make_executable
|
||||||
from testing.fixtures import git_dir
|
from testing.fixtures import git_dir
|
||||||
from testing.fixtures import make_consuming_repo
|
from testing.fixtures import make_consuming_repo
|
||||||
from testing.fixtures import write_config
|
from testing.fixtures import write_config
|
||||||
|
|
@ -134,6 +135,54 @@ def commit_msg_repo(tempdir_factory):
|
||||||
yield path
|
yield path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def prepare_commit_msg_repo(tempdir_factory):
|
||||||
|
path = git_dir(tempdir_factory)
|
||||||
|
script_name = 'add_sign_off.sh'
|
||||||
|
config = {
|
||||||
|
'repo': 'local',
|
||||||
|
'hooks': [{
|
||||||
|
'id': 'add-signoff',
|
||||||
|
'name': 'Add "Signed off by:"',
|
||||||
|
'entry': './{}'.format(script_name),
|
||||||
|
'language': 'script',
|
||||||
|
'stages': ['prepare-commit-msg'],
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
write_config(path, config)
|
||||||
|
with cwd(path):
|
||||||
|
with io.open(script_name, 'w') as script_file:
|
||||||
|
script_file.write(
|
||||||
|
'#!/usr/bin/env bash\n'
|
||||||
|
'set -eu\n'
|
||||||
|
'echo "\nSigned off by: " >> "$1"\n',
|
||||||
|
)
|
||||||
|
make_executable(script_name)
|
||||||
|
cmd_output('git', 'add', '.')
|
||||||
|
git_commit(msg=prepare_commit_msg_repo.__name__)
|
||||||
|
yield path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def failing_prepare_commit_msg_repo(tempdir_factory):
|
||||||
|
path = git_dir(tempdir_factory)
|
||||||
|
config = {
|
||||||
|
'repo': 'local',
|
||||||
|
'hooks': [{
|
||||||
|
'id': 'add-signoff',
|
||||||
|
'name': 'Add "Signed off by:"',
|
||||||
|
'entry': '/usr/bin/env bash -c "exit 1"',
|
||||||
|
'language': 'system',
|
||||||
|
'stages': ['prepare-commit-msg'],
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
write_config(path, config)
|
||||||
|
with cwd(path):
|
||||||
|
cmd_output('git', 'add', '.')
|
||||||
|
git_commit(msg=failing_prepare_commit_msg_repo.__name__)
|
||||||
|
yield path
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope='session')
|
@pytest.fixture(autouse=True, scope='session')
|
||||||
def dont_write_to_home_directory():
|
def dont_write_to_home_directory():
|
||||||
"""pre_commit.store.Store will by default write to the home directory
|
"""pre_commit.store.Store will by default write to the home directory
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue