Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e4ef34e

Browse files
committed
Merge pull request #189 from pre-commit/pre_push
pre-push
2 parents d2b11a0 + febb270 commit e4ef34e

10 files changed

Lines changed: 206 additions & 45 deletions

File tree

pre_commit/commands/install_uninstall.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
PREVIOUS_IDENTIFYING_HASHES = (
2020
'4d9958c90bc262f47553e2c073f14cfe',
2121
'd8ee923c46731b42cd95cc869add4062',
22+
'49fd668cb42069aa1b6048464be5d395',
2223
)
2324

2425

25-
IDENTIFYING_HASH = '49fd668cb42069aa1b6048464be5d395'
26+
IDENTIFYING_HASH = '79f09a650522a87b0da915d0d983b2de'
2627

2728

2829
def is_our_pre_commit(filename):
@@ -42,37 +43,46 @@ def make_executable(filename):
4243
)
4344

4445

45-
def install(runner, overwrite=False, hooks=False):
46+
def install(runner, overwrite=False, hooks=False, hook_type='pre-commit'):
4647
"""Install the pre-commit hooks."""
47-
pre_commit_file = resource_filename('pre-commit-hook')
48+
hook_path = runner.get_hook_path(hook_type)
49+
legacy_path = hook_path + '.legacy'
4850

4951
# If we have an existing hook, move it to pre-commit.legacy
5052
if (
51-
os.path.exists(runner.pre_commit_path) and
52-
not is_our_pre_commit(runner.pre_commit_path) and
53-
not is_previous_pre_commit(runner.pre_commit_path)
53+
os.path.exists(hook_path) and
54+
not is_our_pre_commit(hook_path) and
55+
not is_previous_pre_commit(hook_path)
5456
):
55-
os.rename(runner.pre_commit_path, runner.pre_commit_legacy_path)
57+
os.rename(hook_path, legacy_path)
5658

5759
# If we specify overwrite, we simply delete the legacy file
58-
if overwrite and os.path.exists(runner.pre_commit_legacy_path):
59-
os.remove(runner.pre_commit_legacy_path)
60-
elif os.path.exists(runner.pre_commit_legacy_path):
60+
if overwrite and os.path.exists(legacy_path):
61+
os.remove(legacy_path)
62+
elif os.path.exists(legacy_path):
6163
print(
6264
'Running in migration mode with existing hooks at {0}\n'
6365
'Use -f to use only pre-commit.'.format(
64-
runner.pre_commit_legacy_path,
66+
legacy_path,
6567
)
6668
)
6769

68-
with io.open(runner.pre_commit_path, 'w') as pre_commit_file_obj:
69-
contents = io.open(pre_commit_file).read().format(
70+
with io.open(hook_path, 'w') as pre_commit_file_obj:
71+
if hook_type == 'pre-push':
72+
with io.open(resource_filename('pre-push-tmpl')) as fp:
73+
pre_push_contents = fp.read()
74+
else:
75+
pre_push_contents = ''
76+
77+
contents = io.open(resource_filename('hook-tmpl')).read().format(
7078
sys_executable=sys.executable,
79+
hook_type=hook_type,
80+
pre_push=pre_push_contents,
7181
)
7282
pre_commit_file_obj.write(contents)
73-
make_executable(runner.pre_commit_path)
83+
make_executable(hook_path)
7484

75-
print('pre-commit installed at {0}'.format(runner.pre_commit_path))
85+
print('pre-commit installed at {0}'.format(hook_path))
7686

7787
# If they requested we install all of the hooks, do so.
7888
if hooks:
@@ -85,22 +95,24 @@ def install(runner, overwrite=False, hooks=False):
8595
return 0
8696

8797

88-
def uninstall(runner):
98+
def uninstall(runner, hook_type='pre-commit'):
8999
"""Uninstall the pre-commit hooks."""
100+
hook_path = runner.get_hook_path(hook_type)
101+
legacy_path = hook_path + '.legacy'
90102
# If our file doesn't exist or it isn't ours, gtfo.
91103
if (
92-
not os.path.exists(runner.pre_commit_path) or (
93-
not is_our_pre_commit(runner.pre_commit_path) and
94-
not is_previous_pre_commit(runner.pre_commit_path)
104+
not os.path.exists(hook_path) or (
105+
not is_our_pre_commit(hook_path) and
106+
not is_previous_pre_commit(hook_path)
95107
)
96108
):
97109
return 0
98110

99-
os.remove(runner.pre_commit_path)
100-
print('pre-commit uninstalled')
111+
os.remove(hook_path)
112+
print('{0} uninstalled'.format(hook_type))
101113

102-
if os.path.exists(runner.pre_commit_legacy_path):
103-
os.rename(runner.pre_commit_legacy_path, runner.pre_commit_path)
104-
print('Restored previous hooks to {0}'.format(runner.pre_commit_path))
114+
if os.path.exists(legacy_path):
115+
os.rename(legacy_path, hook_path)
116+
print('Restored previous hooks to {0}'.format(hook_path))
105117

106118
return 0

pre_commit/commands/run.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pre_commit.output import get_hook_message
1212
from pre_commit.output import sys_stdout_write_wrapper
1313
from pre_commit.staged_files_only import staged_files_only
14+
from pre_commit.util import cmd_output
1415
from pre_commit.util import noop_context
1516

1617

@@ -48,8 +49,18 @@ def _print_user_skipped(hook, write, args):
4849
))
4950

5051

52+
def get_changed_files(new, old):
53+
return cmd_output(
54+
'git', 'diff', '--name-only', '{0}..{1}'.format(old, new),
55+
)[1].splitlines()
56+
57+
5158
def _run_single_hook(runner, repository, hook, args, write, skips=set()):
52-
if args.files:
59+
if args.origin and args.source:
60+
get_filenames = git.get_files_matching(
61+
lambda: get_changed_files(args.origin, args.source),
62+
)
63+
elif args.files:
5364
get_filenames = git.get_files_matching(lambda: args.files)
5465
elif args.all_files:
5566
get_filenames = git.get_all_files_matching
@@ -137,6 +148,9 @@ def run(runner, args, write=sys_stdout_write_wrapper, environ=os.environ):
137148
if _has_unmerged_paths(runner):
138149
logger.error('Unmerged files. Resolve before committing.')
139150
return 1
151+
if bool(args.source) != bool(args.origin):
152+
logger.error('Specify both --origin and --source.')
153+
return 1
140154

141155
# Don't stash if specified or files are specified
142156
if args.no_stash or args.all_files or args.files:

pre_commit/main.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,18 @@ def main(argv=None):
4444
'in the config file.'
4545
),
4646
)
47+
install_parser.add_argument(
48+
'-t', '--hook-type', choices=('pre-commit', 'pre-push'),
49+
default='pre-commit',
50+
)
4751

48-
subparsers.add_parser('uninstall', help='Uninstall the pre-commit script.')
52+
uninstall_parser = subparsers.add_parser(
53+
'uninstall', help='Uninstall the pre-commit script.',
54+
)
55+
uninstall_parser.add_argument(
56+
'-t', '--hook-type', choices=('pre-commit', 'pre-push'),
57+
default='pre-commit',
58+
)
4959

5060
subparsers.add_parser('clean', help='Clean out pre-commit files.')
5161

@@ -67,6 +77,15 @@ def main(argv=None):
6777
run_parser.add_argument(
6878
'--verbose', '-v', action='store_true', default=False,
6979
)
80+
81+
run_parser.add_argument(
82+
'--origin', '-o',
83+
help='The origin branch"s commit_id when using `git push`',
84+
)
85+
run_parser.add_argument(
86+
'--source', '-s',
87+
help='The remote branch"s commit_id when using `git push`',
88+
)
7089
run_mutex_group = run_parser.add_mutually_exclusive_group(required=False)
7190
run_mutex_group.add_argument(
7291
'--all-files', '-a', action='store_true', default=False,
@@ -98,9 +117,10 @@ def main(argv=None):
98117
if args.command == 'install':
99118
return install(
100119
runner, overwrite=args.overwrite, hooks=args.install_hooks,
120+
hook_type=args.hook_type,
101121
)
102122
elif args.command == 'uninstall':
103-
return uninstall(runner)
123+
return uninstall(runner, hook_type=args.hook_type)
104124
elif args.command == 'clean':
105125
return clean(runner)
106126
elif args.command == 'autoupdate':
Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#!/usr/bin/env bash
22
# This is a randomish md5 to identify this script
3-
# 49fd668cb42069aa1b6048464be5d395
3+
# 79f09a650522a87b0da915d0d983b2de
44

55
pushd `dirname $0` > /dev/null
66
HERE=`pwd`
77
popd > /dev/null
88

99
retv=0
10+
args=""
1011

1112
ENV_PYTHON='{sys_executable}'
1213

@@ -23,29 +24,30 @@ if ((
2324
(ENV_PYTHON_RETV != 0) &&
2425
(PYTHON_RETV != 0)
2526
)); then
26-
echo '`pre-commit` not found. Did you forget to activate your virtualenv?'
27+
echo '`{hook_type}` not found. Did you forget to activate your virtualenv?'
2728
exit 1
2829
fi
2930

3031

3132
# Run the legacy pre-commit if it exists
32-
if [ -x "$HERE"/pre-commit.legacy ]; then
33-
"$HERE"/pre-commit.legacy
33+
if [ -x "$HERE"/{hook_type}.legacy ]; then
34+
"$HERE"/{hook_type}.legacy
3435
if [ $? -ne 0 ]; then
3536
retv=1
3637
fi
3738
fi
3839

40+
{pre_push}
3941

4042
# Run pre-commit
4143
if ((WHICH_RETV == 0)); then
42-
pre-commit
44+
pre-commit $args
4345
PRE_COMMIT_RETV=$?
4446
elif ((ENV_PYTHON_RETV == 0)); then
45-
"$ENV_PYTHON" -m pre_commit.main
47+
"$ENV_PYTHON" -m pre_commit.main $args
4648
PRE_COMMIT_RETV=$?
4749
else
48-
python -m pre_commit.main
50+
python -m pre_commit.main $args
4951
PRE_COMMIT_RETV=$?
5052
fi
5153

pre_commit/resources/pre-push-tmpl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
z40=0000000000000000000000000000000000000000
2+
while read local_ref local_sha remote_ref remote_sha
3+
do
4+
if [ "$local_sha" != $z40 ]; then
5+
if [ "$remote_sha" = $z40 ];
6+
then
7+
args="run --all-files"
8+
else
9+
args="run --origin $local_sha --source $remote_sha"
10+
fi
11+
fi
12+
done

pre_commit/runner.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,16 @@ def repositories(self):
4343
repository.require_installed()
4444
return repositories
4545

46+
def get_hook_path(self, hook_type):
47+
return os.path.join(self.git_root, '.git', 'hooks', hook_type)
48+
4649
@cached_property
4750
def pre_commit_path(self):
48-
return os.path.join(self.git_root, '.git', 'hooks', 'pre-commit')
51+
return self.get_hook_path('pre-commit')
4952

5053
@cached_property
51-
def pre_commit_legacy_path(self):
52-
"""The path in the 'hooks' directory representing the temporary
53-
storage for existing pre-commit hooks.
54-
"""
55-
return self.pre_commit_path + '.legacy'
54+
def pre_push_path(self):
55+
return self.get_hook_path('pre-push')
5656

5757
@cached_property
5858
def cmd_runner(self):

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
packages=find_packages('.', exclude=('tests*', 'testing*')),
3131
package_data={
3232
'pre_commit': [
33-
'resources/pre-commit-hook',
33+
'resources/hook-tmpl',
34+
'resources/pre-push-tmpl',
3435
'resources/rbenv.tar.gz',
3536
'resources/ruby-build.tar.gz',
3637
'resources/ruby-download.tar.gz',

tests/commands/install_uninstall_test.py

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ def test_is_not_our_pre_commit():
3131

3232

3333
def test_is_our_pre_commit():
34-
assert is_our_pre_commit(resource_filename('pre-commit-hook'))
34+
assert is_our_pre_commit(resource_filename('hook-tmpl'))
3535

3636

3737
def test_is_not_previous_pre_commit():
3838
assert is_previous_pre_commit('setup.py') is False
3939

4040

4141
def test_is_also_not_previous_pre_commit():
42-
assert not is_previous_pre_commit(resource_filename('pre-commit-hook'))
42+
assert not is_previous_pre_commit(resource_filename('hook-tmpl'))
4343

4444

4545
def test_is_previous_pre_commit(in_tmpdir):
@@ -56,14 +56,29 @@ def test_install_pre_commit(tmpdir_factory):
5656
assert ret == 0
5757
assert os.path.exists(runner.pre_commit_path)
5858
pre_commit_contents = io.open(runner.pre_commit_path).read()
59-
pre_commit_script = resource_filename('pre-commit-hook')
59+
pre_commit_script = resource_filename('hook-tmpl')
6060
expected_contents = io.open(pre_commit_script).read().format(
6161
sys_executable=sys.executable,
62+
hook_type='pre-commit',
63+
pre_push=''
6264
)
6365
assert pre_commit_contents == expected_contents
6466
stat_result = os.stat(runner.pre_commit_path)
6567
assert stat_result.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
6668

69+
ret = install(runner, hook_type='pre-push')
70+
assert ret == 0
71+
assert os.path.exists(runner.pre_push_path)
72+
pre_push_contents = io.open(runner.pre_push_path).read()
73+
pre_push_tmpl = resource_filename('pre-push-tmpl')
74+
pre_push_template_contents = io.open(pre_push_tmpl).read()
75+
expected_contents = io.open(pre_commit_script).read().format(
76+
sys_executable=sys.executable,
77+
hook_type='pre-push',
78+
pre_push=pre_push_template_contents,
79+
)
80+
assert pre_push_contents == expected_contents
81+
6782

6883
def test_uninstall_does_not_blow_up_when_not_there(tmpdir_factory):
6984
path = git_dir(tmpdir_factory)
@@ -322,7 +337,7 @@ def test_replace_old_commit_script(tmpdir_factory):
322337

323338
# Install a script that looks like our old script
324339
pre_commit_contents = io.open(
325-
resource_filename('pre-commit-hook'),
340+
resource_filename('hook-tmpl'),
326341
).read()
327342
new_contents = pre_commit_contents.replace(
328343
IDENTIFYING_HASH, PREVIOUS_IDENTIFYING_HASHES[-1],
@@ -391,3 +406,46 @@ def test_installed_from_venv(tmpdir_factory):
391406
)
392407
assert ret == 0
393408
assert NORMAL_PRE_COMMIT_RUN.match(output)
409+
410+
411+
def _get_push_output(tmpdir_factory):
412+
# Don't want to write to home directory
413+
home = tmpdir_factory.get()
414+
env = dict(os.environ, **{'PRE_COMMIT_HOME': home})
415+
return cmd_output(
416+
'git', 'push', 'origin', 'HEAD:new_branch',
417+
# git commit puts pre-commit to stderr
418+
stderr=subprocess.STDOUT,
419+
env=env,
420+
retcode=None,
421+
)[:2]
422+
423+
424+
def test_pre_push_integration_failing(tmpdir_factory):
425+
upstream = make_consuming_repo(tmpdir_factory, 'failing_hook_repo')
426+
path = tmpdir_factory.get()
427+
cmd_output('git', 'clone', upstream, path)
428+
with cwd(path):
429+
install(Runner(path), hook_type='pre-push')
430+
# commit succeeds because pre-commit is only installed for pre-push
431+
assert _get_commit_output(tmpdir_factory)[0] == 0
432+
433+
retc, output = _get_push_output(tmpdir_factory)
434+
assert retc == 1
435+
assert 'Failing hook' in output
436+
assert 'Failed' in output
437+
assert 'hookid: failing_hook' in output
438+
439+
440+
def test_pre_push_integration_accepted(tmpdir_factory):
441+
upstream = make_consuming_repo(tmpdir_factory, 'script_hooks_repo')
442+
path = tmpdir_factory.get()
443+
cmd_output('git', 'clone', upstream, path)
444+
with cwd(path):
445+
install(Runner(path), hook_type='pre-push')
446+
assert _get_commit_output(tmpdir_factory)[0] == 0
447+
448+
retc, output = _get_push_output(tmpdir_factory)
449+
assert retc == 0
450+
assert 'Bash hook' in output
451+
assert 'Passed' in output

0 commit comments

Comments
 (0)