Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for post-merge hooks #1800

Merged
merged 1 commit into from Feb 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions pre_commit/commands/hook_impl.py
Expand Up @@ -76,6 +76,7 @@ def _ns(
remote_url: Optional[str] = None,
commit_msg_filename: Optional[str] = None,
checkout_type: Optional[str] = None,
is_squash_merge: Optional[str] = None,
) -> argparse.Namespace:
return argparse.Namespace(
color=color,
Expand All @@ -88,6 +89,7 @@ def _ns(
commit_msg_filename=commit_msg_filename,
all_files=all_files,
checkout_type=checkout_type,
is_squash_merge=is_squash_merge,
files=(),
hook=None,
verbose=False,
Expand Down Expand Up @@ -158,6 +160,7 @@ def _pre_push_ns(
'post-commit': 0,
'pre-commit': 0,
'pre-merge-commit': 0,
'post-merge': 1,
'pre-push': 2,
}

Expand Down Expand Up @@ -199,6 +202,8 @@ def _run_ns(
hook_type, color,
from_ref=args[0], to_ref=args[1], checkout_type=args[2],
)
elif hook_type == 'post-merge':
return _ns(hook_type, color, is_squash_merge=args[0])
else:
raise AssertionError(f'unexpected hook type: {hook_type}')

Expand Down
5 changes: 4 additions & 1 deletion pre_commit/commands/run.py
Expand Up @@ -245,7 +245,7 @@ def _compute_cols(hooks: Sequence[Hook]) -> int:

def _all_filenames(args: argparse.Namespace) -> Collection[str]:
# these hooks do not operate on files
if args.hook_stage in {'post-checkout', 'post-commit'}:
if args.hook_stage in {'post-checkout', 'post-commit', 'post-merge'}:
return ()
elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}:
return (args.commit_msg_filename,)
Expand Down Expand Up @@ -379,6 +379,9 @@ def run(
if args.checkout_type:
environ['PRE_COMMIT_CHECKOUT_TYPE'] = args.checkout_type

if args.is_squash_merge:
environ['PRE_COMMIT_IS_SQUASH_MERGE'] = args.is_squash_merge

# Set pre_commit flag
environ['PRE_COMMIT'] = '1'

Expand Down
2 changes: 1 addition & 1 deletion pre_commit/constants.py
Expand Up @@ -18,7 +18,7 @@
# `manual` is not invoked by any installed git hook. See #719
STAGES = (
'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg',
'post-commit', 'manual', 'post-checkout', 'push',
'post-commit', 'manual', 'post-checkout', 'push', 'post-merge',
)

DEFAULT = 'default'
11 changes: 9 additions & 2 deletions pre_commit/main.py
Expand Up @@ -67,8 +67,8 @@ def __call__(
def _add_hook_type_option(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'-t', '--hook-type', choices=(
'pre-commit', 'pre-merge-commit', 'pre-push',
'prepare-commit-msg', 'commit-msg', 'post-commit', 'post-checkout',
'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg',
'commit-msg', 'post-commit', 'post-checkout', 'post-merge',
),
action=AppendReplaceDefault,
default=['pre-commit'],
Expand Down Expand Up @@ -136,6 +136,13 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
'file from the index, flag=0).'
),
)
parser.add_argument(
'--is-squash-merge',
help=(
'During a post-merge hook, indicates whether the merge was a '
'squash merge'
),
)


def _adjust_args_and_chdir(args: argparse.Namespace) -> None:
Expand Down
2 changes: 2 additions & 0 deletions testing/util.py
Expand Up @@ -70,6 +70,7 @@ def run_opts(
show_diff_on_failure=False,
commit_msg_filename='',
checkout_type='',
is_squash_merge='',
):
# These are mutually exclusive
assert not (all_files and files)
Expand All @@ -88,6 +89,7 @@ def run_opts(
show_diff_on_failure=show_diff_on_failure,
commit_msg_filename=commit_msg_filename,
checkout_type=checkout_type,
is_squash_merge=is_squash_merge,
)


Expand Down
9 changes: 9 additions & 0 deletions tests/commands/hook_impl_test.py
Expand Up @@ -97,6 +97,7 @@ def call(*_, **__):
('pre-push', ['branch_name', 'remote_name']),
('commit-msg', ['.git/COMMIT_EDITMSG']),
('post-commit', []),
('post-merge', ['1']),
('post-checkout', ['old_head', 'new_head', '1']),
# multiple choices for commit-editmsg
('prepare-commit-msg', ['.git/COMMIT_EDITMSG']),
Expand Down Expand Up @@ -157,6 +158,14 @@ def test_run_ns_post_commit():
assert ns.color is True


def test_run_ns_post_merge():
ns = hook_impl._run_ns('post-merge', True, ('1',), b'')
assert ns is not None
assert ns.hook_stage == 'post-merge'
assert ns.color is True
assert ns.is_squash_merge == '1'


def test_run_ns_post_checkout():
ns = hook_impl._run_ns('post-checkout', True, ('a', 'b', 'c'), b'')
assert ns is not None
Expand Down
42 changes: 42 additions & 0 deletions tests/commands/install_uninstall_test.py
Expand Up @@ -759,6 +759,48 @@ def test_post_commit_integration(tempdir_factory, store):
assert os.path.exists('post-commit.tmp')


def test_post_merge_integration(tempdir_factory, store):
path = git_dir(tempdir_factory)
config = [
{
'repo': 'local',
'hooks': [{
'id': 'post-merge',
'name': 'Post merge',
'entry': 'touch post-merge.tmp',
'language': 'system',
'always_run': True,
'verbose': True,
'stages': ['post-merge'],
}],
},
]
write_config(path, config)
with cwd(path):
# create a simple diamond of commits for a non-trivial merge
open('init', 'a').close()
cmd_output('git', 'add', '.')
git_commit()

open('master', 'a').close()
cmd_output('git', 'add', '.')
git_commit()

cmd_output('git', 'checkout', '-b', 'branch', 'HEAD^')
open('branch', 'a').close()
cmd_output('git', 'add', '.')
git_commit()

cmd_output('git', 'checkout', 'master')
install(C.CONFIG_FILE, store, hook_types=['post-merge'])
retc, stdout, stderr = cmd_output_mocked_pre_commit_home(
'git', 'merge', 'branch',
tempdir_factory=tempdir_factory,
)
assert retc == 0
assert os.path.exists('post-merge.tmp')


def test_post_checkout_integration(tempdir_factory, store):
path = git_dir(tempdir_factory)
config = [
Expand Down
9 changes: 9 additions & 0 deletions tests/commands/run_test.py
Expand Up @@ -494,6 +494,15 @@ def test_all_push_options_ok(cap_out, store, repo_with_passing_hook):
assert b'Specify both --from-ref and --to-ref.' not in printed


def test_is_squash_merge(cap_out, store, repo_with_passing_hook):
args = run_opts(is_squash_merge='1')
environ: MutableMapping[str, str] = {}
ret, printed = _do_run(
cap_out, store, repo_with_passing_hook, args, environ,
)
assert environ['PRE_COMMIT_IS_SQUASH_MERGE'] == '1'


def test_checkout_type(cap_out, store, repo_with_passing_hook):
args = run_opts(from_ref='', to_ref='', checkout_type='1')
environ: MutableMapping[str, str] = {}
Expand Down
2 changes: 1 addition & 1 deletion tests/repository_test.py
Expand Up @@ -953,7 +953,7 @@ def test_manifest_hooks(tempdir_factory, store):
require_serial=False,
stages=(
'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg',
'post-commit', 'manual', 'post-checkout', 'push',
'post-commit', 'manual', 'post-checkout', 'push', 'post-merge',
),
types=['file'],
types_or=[],
Expand Down