Add post-checkout

This commit is contained in:
Andrew Hare 2020-02-20 02:21:29 -07:00 committed by Anthony Sottile
parent 1c641b1c28
commit 18fa004254
9 changed files with 77 additions and 5 deletions

View file

@ -74,6 +74,7 @@ def _ns(
remote_name: Optional[str] = None, remote_name: Optional[str] = None,
remote_url: Optional[str] = None, remote_url: Optional[str] = None,
commit_msg_filename: Optional[str] = None, commit_msg_filename: Optional[str] = None,
checkout_type: Optional[str] = None,
) -> argparse.Namespace: ) -> argparse.Namespace:
return argparse.Namespace( return argparse.Namespace(
color=color, color=color,
@ -84,6 +85,7 @@ def _ns(
remote_url=remote_url, remote_url=remote_url,
commit_msg_filename=commit_msg_filename, commit_msg_filename=commit_msg_filename,
all_files=all_files, all_files=all_files,
checkout_type=checkout_type,
files=(), files=(),
hook=None, hook=None,
verbose=False, verbose=False,
@ -157,6 +159,11 @@ def _run_ns(
return _ns(hook_type, color, commit_msg_filename=args[0]) return _ns(hook_type, color, commit_msg_filename=args[0])
elif hook_type in {'pre-merge-commit', 'pre-commit'}: elif hook_type in {'pre-merge-commit', 'pre-commit'}:
return _ns(hook_type, color) return _ns(hook_type, color)
elif hook_type == 'post-checkout':
return _ns(
hook_type, color, source=args[0], origin=args[1],
checkout_type=args[2],
)
else: else:
raise AssertionError(f'unexpected hook type: {hook_type}') raise AssertionError(f'unexpected hook type: {hook_type}')

View file

@ -316,6 +316,9 @@ def run(
environ['PRE_COMMIT_REMOTE_NAME'] = args.remote_name environ['PRE_COMMIT_REMOTE_NAME'] = args.remote_name
environ['PRE_COMMIT_REMOTE_URL'] = args.remote_url environ['PRE_COMMIT_REMOTE_URL'] = args.remote_url
if args.checkout_type:
environ['PRE_COMMIT_CHECKOUT_TYPE'] = args.checkout_type
with contextlib.ExitStack() as exit_stack: with contextlib.ExitStack() as exit_stack:
if stash: if stash:
exit_stack.enter_context(staged_files_only(store.directory)) exit_stack.enter_context(staged_files_only(store.directory))

View file

@ -18,7 +18,7 @@ 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 = ( STAGES = (
'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', 'manual', 'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', 'manual',
'push', 'post-checkout', 'push',
) )
DEFAULT = 'default' DEFAULT = 'default'

View file

@ -79,7 +79,7 @@ def _add_hook_type_option(parser: argparse.ArgumentParser) -> None:
parser.add_argument( parser.add_argument(
'-t', '--hook-type', choices=( '-t', '--hook-type', choices=(
'pre-commit', 'pre-merge-commit', 'pre-push', 'pre-commit', 'pre-merge-commit', 'pre-push',
'prepare-commit-msg', 'commit-msg', 'prepare-commit-msg', 'commit-msg', 'post-checkout',
), ),
action=AppendReplaceDefault, action=AppendReplaceDefault,
default=['pre-commit'], default=['pre-commit'],
@ -92,11 +92,17 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument('--verbose', '-v', action='store_true', default=False) parser.add_argument('--verbose', '-v', action='store_true', default=False)
parser.add_argument( parser.add_argument(
'--origin', '-o', '--origin', '-o',
help="The origin branch's commit_id when using `git push`.", help=(
"The origin branch's commit_id when using `git push`. "
'The ref of the previous HEAD when using `git checkout`.'
),
) )
parser.add_argument( parser.add_argument(
'--source', '-s', '--source', '-s',
help="The remote branch's commit_id when using `git push`.", help=(
"The remote branch's commit_id when using `git push`. "
'The ref of the new HEAD when using `git checkout`.'
),
) )
parser.add_argument( parser.add_argument(
'--commit-msg-filename', '--commit-msg-filename',
@ -123,6 +129,14 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
'--files', nargs='*', default=[], '--files', nargs='*', default=[],
help='Specific filenames to run hooks on.', help='Specific filenames to run hooks on.',
) )
parser.add_argument(
'--checkout-type',
help=(
'Indicates whether the checkout was a branch checkout '
'(changing branches, flag=1) or a file checkout (retrieving a '
'file from the index, flag=0).'
),
)
def _adjust_args_and_chdir(args: argparse.Namespace) -> None: def _adjust_args_and_chdir(args: argparse.Namespace) -> None:

View file

@ -72,6 +72,7 @@ def run_opts(
hook_stage='commit', hook_stage='commit',
show_diff_on_failure=False, show_diff_on_failure=False,
commit_msg_filename='', commit_msg_filename='',
checkout_type='',
): ):
# These are mutually exclusive # These are mutually exclusive
assert not (all_files and files) assert not (all_files and files)
@ -88,6 +89,7 @@ def run_opts(
hook_stage=hook_stage, hook_stage=hook_stage,
show_diff_on_failure=show_diff_on_failure, show_diff_on_failure=show_diff_on_failure,
commit_msg_filename=commit_msg_filename, commit_msg_filename=commit_msg_filename,
checkout_type=checkout_type,
) )

View file

@ -104,6 +104,16 @@ def test_run_ns_commit_msg():
assert ns.commit_msg_filename == '.git/COMMIT_MSG' assert ns.commit_msg_filename == '.git/COMMIT_MSG'
def test_run_ns_post_checkout():
ns = hook_impl._run_ns('post-checkout', True, ('a', 'b', 'c'), b'')
assert ns is not None
assert ns.hook_stage == 'post-checkout'
assert ns.color is True
assert ns.source == 'a'
assert ns.origin == 'b'
assert ns.checkout_type == 'c'
@pytest.fixture @pytest.fixture
def push_example(tempdir_factory): def push_example(tempdir_factory):
src = git_dir(tempdir_factory) src = git_dir(tempdir_factory)

View file

@ -20,6 +20,7 @@ from testing.fixtures import add_config_to_repo
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 remove_config_from_repo from testing.fixtures import remove_config_from_repo
from testing.fixtures import write_config
from testing.util import cmd_output_mocked_pre_commit_home from testing.util import cmd_output_mocked_pre_commit_home
from testing.util import cwd from testing.util import cwd
from testing.util import git_commit from testing.util import git_commit
@ -725,6 +726,31 @@ 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_post_checkout_integration(tempdir_factory, store):
path = git_dir(tempdir_factory)
config = {
'repo': 'local',
'hooks': [{
'id': 'post-checkout',
'name': 'Post checkout',
'entry': 'bash -c "echo ${PRE_COMMIT_ORIGIN}"',
'language': 'system',
'always_run': True,
'verbose': True,
'stages': ['post-checkout'],
}],
}
write_config(path, config)
with cwd(path):
cmd_output('git', 'add', '.')
git_commit()
install(C.CONFIG_FILE, store, hook_types=['post-checkout'])
retc, _, stderr = cmd_output('git', 'checkout', '-b', 'feature')
assert retc == 0
_, head, _ = cmd_output('git', 'rev-parse', 'HEAD')
assert head in str(stderr)
def test_prepare_commit_msg_integration_failing( def test_prepare_commit_msg_integration_failing(
failing_prepare_commit_msg_repo, tempdir_factory, store, failing_prepare_commit_msg_repo, tempdir_factory, store,
): ):

View file

@ -18,6 +18,7 @@ from pre_commit.commands.run import Classifier
from pre_commit.commands.run import filter_by_include_exclude from pre_commit.commands.run import filter_by_include_exclude
from pre_commit.commands.run import run from pre_commit.commands.run import run
from pre_commit.util import cmd_output from pre_commit.util import cmd_output
from pre_commit.util import EnvironT
from pre_commit.util import make_executable from pre_commit.util import make_executable
from testing.auto_namedtuple import auto_namedtuple from testing.auto_namedtuple import auto_namedtuple
from testing.fixtures import add_config_to_repo from testing.fixtures import add_config_to_repo
@ -466,6 +467,15 @@ def test_all_push_options_ok(cap_out, store, repo_with_passing_hook):
assert b'Specify both --origin and --source.' not in printed assert b'Specify both --origin and --source.' not in printed
def test_checkout_type(cap_out, store, repo_with_passing_hook):
args = run_opts(origin='', source='', checkout_type='1')
environ: EnvironT = {}
ret, printed = _do_run(
cap_out, store, repo_with_passing_hook, args, environ,
)
assert environ['PRE_COMMIT_CHECKOUT_TYPE'] == '1'
def test_has_unmerged_paths(in_merge_conflict): def test_has_unmerged_paths(in_merge_conflict):
assert _has_unmerged_paths() is True assert _has_unmerged_paths() is True
cmd_output('git', 'add', '.') cmd_output('git', 'add', '.')

View file

@ -871,7 +871,7 @@ def test_manifest_hooks(tempdir_factory, store):
require_serial=False, require_serial=False,
stages=( stages=(
'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', 'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg',
'manual', 'push', 'manual', 'post-checkout', 'push',
), ),
types=['file'], types=['file'],
verbose=False, verbose=False,