Skip to content

Instantly share code, notes, and snippets.

@sleifer
Forked from carlos-jenkins/multihooks.py
Last active December 13, 2017 12:44
Show Gist options
  • Save sleifer/35478a4ba2425a01dca41819794d5092 to your computer and use it in GitHub Desktop.
Save sleifer/35478a4ba2425a01dca41819794d5092 to your computer and use it in GitHub Desktop.
Delegating script for multiple git hooks
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2015 Carlos Jenkins <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Support for passing args and stdin on to sub scripts added by Simeon Leifer 3 Jun 2016
"""
Small delegating Python script to allow multiple hooks in a git repository.
Usage:
Make your building system to create a symbolic link in the git hooks directory
to this script with the name of the hook you want to attend. For example,
``pre-commit``.
This hook will then execute, in alphabetic order, all executables files
(subhooks) found under a folder named after the hook type you're attending
suffixed with ``.d``. For example, ``pre-commit.d``.
For example:
```
.git/hooks/
|_ pre-commit
|_ pre-commit.d/
|_ 01-cpp_coding_standard
|_ 02-python_coding_standard
|_ 03-something_else
```
"""
from subprocess import Popen, PIPE
from os import access, listdir, X_OK
from logging import getLogger, basicConfig, DEBUG
from os.path import isfile, isdir, abspath, normpath, dirname, join, basename
import sys
from tempfile import TemporaryFile
import select
GIT_HOOKS = [
'applypatch-msg',
'commit-msg',
'post-update',
'pre-applypatch',
'pre-commit',
'prepare-commit-msg',
'pre-push',
'pre-rebase',
'update',
]
basicConfig(level=DEBUG)
log = getLogger('multihooks')
def main():
"""
Execute subhooks for the assigned hook type.
"""
# Check multihooks facing what hook type
hook_type = basename(__file__)
if hook_type not in GIT_HOOKS:
log.fatal('Unknown hook type: {}'.format(hook_type))
exit(1)
# Lookup for sub-hooks directory
root = normpath(abspath(dirname(__file__)))
hooks_dir = join(root, '{}.d'.format(hook_type))
if not isdir(hooks_dir):
log.warning('No such directory: {}'.format(hooks_dir))
exit(0)
# Gather scripts to call
files = [join(hooks_dir, f) for f in listdir(hooks_dir)]
hooks = sorted(
[h for h in files if isfile(h) and access(h, X_OK)]
)
if not hooks:
log.warning('No sub-hooks found for {}.'.format(hook_type))
exit(0)
with TemporaryFile(mode='w') as f:
while sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
line = sys.stdin.readline()
if line:
f.write(line)
else:
break
# Execute hooks
for h in hooks:
hook_id = '{}.d/{}'.format(hook_type, basename(h))
log.info('Running hook {}...'.format(hook_id))
args = list(sys.argv)
args[0] = h
f.seek(0)
proc = Popen(args, stdin=f, stdout=PIPE, stderr=PIPE)
stdout, stderr = proc.communicate()
if stdout:
log.info(stdout.decode('utf-8'))
if stderr:
log.error(stderr.decode('utf-8'))
# Log errors if a hook failed
if proc.returncode != 0:
log.info('Hook {} failed. Aborting...'.format(hook_id))
exit(proc.returncode)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment