Skip to content

Commit b707cbb

Browse files
dongweimingasottile
authored andcommitted
Make pre_commit also support pre-push hook
1 parent d2b11a0 commit b707cbb

File tree

10 files changed

+225
-40
lines changed

10 files changed

+225
-40
lines changed

pre_commit/commands/install_uninstall.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import sys
1010

1111
from pre_commit.logging_handler import LoggingHandler
12-
from pre_commit.util import resource_filename
1312

1413

1514
logger = logging.getLogger('pre_commit')
@@ -42,37 +41,55 @@ def make_executable(filename):
4241
)
4342

4443

45-
def install(runner, overwrite=False, hooks=False):
44+
def get_hook_path(runner, hook_type):
45+
if hook_type == 'pre-commit':
46+
hook_path = runner.pre_commit_path
47+
legacy_path = runner.pre_commit_legacy_path
48+
else:
49+
hook_path = runner.pre_push_path
50+
legacy_path = runner.pre_push_legacy_path
51+
return hook_path, legacy_path
52+
53+
54+
def install(runner, overwrite=False, hooks=False, hook_type='pre-commit'):
4655
"""Install the pre-commit hooks."""
47-
pre_commit_file = resource_filename('pre-commit-hook')
56+
hook_path, legacy_path = get_hook_path(runner, hook_type)
4857

4958
# If we have an existing hook, move it to pre-commit.legacy
5059
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)
60+
os.path.exists(hook_path) and
61+
not is_our_pre_commit(hook_path) and
62+
not is_previous_pre_commit(hook_path)
5463
):
55-
os.rename(runner.pre_commit_path, runner.pre_commit_legacy_path)
64+
os.rename(hook_path, legacy_path)
5665

5766
# 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):
67+
if overwrite and os.path.exists(legacy_path):
68+
os.remove(legacy_path)
69+
elif os.path.exists(legacy_path):
6170
print(
6271
'Running in migration mode with existing hooks at {0}\n'
6372
'Use -f to use only pre-commit.'.format(
64-
runner.pre_commit_legacy_path,
73+
legacy_path,
6574
)
6675
)
6776

68-
with io.open(runner.pre_commit_path, 'w') as pre_commit_file_obj:
69-
contents = io.open(pre_commit_file).read().format(
77+
with io.open(hook_path, 'w') as pre_commit_file_obj:
78+
if hook_type == 'pre-push':
79+
with io.open(runner.pre_push_template) as fp:
80+
pre_push_contents = fp.read()
81+
else:
82+
pre_push_contents = ''
83+
84+
contents = io.open(runner.pre_template).read().format(
7085
sys_executable=sys.executable,
86+
hook_type=hook_type,
87+
pre_push=pre_push_contents,
7188
)
7289
pre_commit_file_obj.write(contents)
73-
make_executable(runner.pre_commit_path)
90+
make_executable(hook_path)
7491

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

7794
# If they requested we install all of the hooks, do so.
7895
if hooks:
@@ -85,22 +102,23 @@ def install(runner, overwrite=False, hooks=False):
85102
return 0
86103

87104

88-
def uninstall(runner):
105+
def uninstall(runner, hook_type='pre-commit'):
89106
"""Uninstall the pre-commit hooks."""
107+
hook_path, legacy_path = get_hook_path(runner, hook_type)
90108
# If our file doesn't exist or it isn't ours, gtfo.
91109
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)
110+
not os.path.exists(hook_path) or (
111+
not is_our_pre_commit(hook_path) and
112+
not is_previous_pre_commit(hook_path)
95113
)
96114
):
97115
return 0
98116

99-
os.remove(runner.pre_commit_path)
100-
print('pre-commit uninstalled')
117+
os.remove(hook_path)
118+
print('{0} uninstalled'.format(hook_type))
101119

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))
120+
if os.path.exists(legacy_path):
121+
os.rename(legacy_path, hook_path)
122+
print('Restored previous hooks to {0}'.format(hook_path))
105123

106124
return 0

pre_commit/commands/run.py

Lines changed: 18 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,20 @@ def _print_user_skipped(hook, write, args):
4849
))
4950

5051

52+
def get_changed_files(new, old):
53+
changed_files = cmd_output(
54+
'git', 'diff', '--name-only', '{0}..{1}'.format(old, new),
55+
)[1].splitlines()
56+
for f in changed_files:
57+
if f:
58+
yield f
59+
60+
5161
def _run_single_hook(runner, repository, hook, args, write, skips=set()):
52-
if args.files:
62+
if args.origin and args.source:
63+
get_filenames = git.get_files_matching(
64+
lambda: get_changed_files(args.origin, args.source))
65+
elif args.files:
5366
get_filenames = git.get_files_matching(lambda: args.files)
5467
elif args.all_files:
5568
get_filenames = git.get_all_files_matching
@@ -137,6 +150,10 @@ def run(runner, args, write=sys_stdout_write_wrapper, environ=os.environ):
137150
if _has_unmerged_paths(runner):
138151
logger.error('Unmerged files. Resolve before committing.')
139152
return 1
153+
if (args.source and not args.origin) or \
154+
(args.origin and not args.source):
155+
logger.error('--origin and --source depend on each other.')
156+
return 1
140157

141158
# Don't stash if specified or files are specified
142159
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', default='',
83+
help='The origin branch"s commit_id when using `git push`',
84+
)
85+
run_parser.add_argument(
86+
'--source', '-s', default='',
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: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ 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: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pre_commit.clientlib.validate_config import load_config
1111
from pre_commit.repository import Repository
1212
from pre_commit.store import Store
13+
from pre_commit.util import resource_filename
1314

1415

1516
class Runner(object):
@@ -43,17 +44,39 @@ def repositories(self):
4344
repository.require_installed()
4445
return repositories
4546

47+
def get_hook_path(self, hook_type):
48+
return os.path.join(self.git_root, '.git', 'hooks', hook_type)
49+
4650
@cached_property
4751
def pre_commit_path(self):
48-
return os.path.join(self.git_root, '.git', 'hooks', 'pre-commit')
52+
return self.get_hook_path('pre-commit')
53+
54+
@cached_property
55+
def pre_push_path(self):
56+
return self.get_hook_path('pre-push')
57+
58+
@cached_property
59+
def pre_template(self):
60+
return resource_filename('hook-tmpl')
4961

5062
@cached_property
63+
def pre_push_template(self):
64+
return resource_filename('pre-push-tmpl')
65+
66+
@property
5167
def pre_commit_legacy_path(self):
5268
"""The path in the 'hooks' directory representing the temporary
5369
storage for existing pre-commit hooks.
5470
"""
5571
return self.pre_commit_path + '.legacy'
5672

73+
@property
74+
def pre_push_legacy_path(self):
75+
"""The path in the 'hooks' directory representing the temporary
76+
storage for existing pre-push hooks.
77+
"""
78+
return self.pre_push_path + '.legacy'
79+
5780
@cached_property
5881
def cmd_runner(self):
5982
# TODO: remove this and inline runner.store.cmd_runner

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',

0 commit comments

Comments
 (0)