-
Dr. Felix Tobias Schindler authoredDr. Felix Tobias Schindler authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
pre-commit 3.27 KiB
#!/usr/bin/env python3
"""
This pre-commit hook checks each staged file if it complies to the
formatting rules, as defined by clang-format (or the programm specified
in `git config hooks.clangformat`).
"""
import os
import subprocess
import sys
import tempfile
CPP_EXTENSIONS = ('.c', '.cc', '.cxx', '.h', '.hh', '.hxx', '.pbh')
class FormatRunException(RuntimeError):
pass
class NonEmptyDiffException(Exception):
pass
def check_dir(basedir, mode='staged'):
"""
:param basedir: absolute path to git root
:param mode: anyof 'staged', 'modified' or 'all' to select which files to check. Note
staged files do not count as modified
:return: list of filename with non-empty diff to current state after applying clang-format
"""
commands = {'staged': ['git', 'diff', '--name-only', '--cached'],
'modified': ['git', 'ls-files', '-m'],
'all': ['git', 'ls-files']}
os.chdir(basedir)
# alternative clang-format binary given?
try:
clangformat = subprocess.check_output(['git', 'config', 'hooks.clangformat'],
universal_newlines=True).strip()
except subprocess.CalledProcessError as _:
clangformat = 'clang-format'
out = subprocess.check_output(commands[mode],
universal_newlines=True)
fails = []
for changed_file in sorted(out.splitlines()):
try:
check_file(changed_file, basedir, clangformat)
except NonEmptyDiffException:
fails.append(changed_file)
return fails
def check_file(touched_file, basedir, format_binary, extension_whitelist=CPP_EXTENSIONS):
if os.path.splitext(touched_file)[1] not in extension_whitelist:
return
filename = os.path.join(basedir, touched_file)
if not os.path.isfile(filename):
return
try:
out = subprocess.check_output([format_binary, '-style=file', filename],
universal_newlines=True)
except subprocess.CalledProcessError as e:
raise FormatRunException('Error executing {} on file\n{}'.format(format_binary, filename, e.output))
tmp_filename = tempfile.NamedTemporaryFile(mode='wb', delete=False).name
with open(tmp_filename, 'w') as tmp_file:
tmp_file.write(out)
try:
subprocess.check_output(['diff', filename, tmp_filename],
universal_newlines=True)
except subprocess.CalledProcessError as res:
if res.returncode == 1:
raise NonEmptyDiffException('clang-format has to be applied to \'{}\'!'.format(filename))
raise RuntimeError('error diffing {} vs {}'.format(touched_file, tmp_filename))
finally:
os.remove(tmp_filename)
if __name__ == '__main__':
dirname = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else os.getcwd())
try:
fails = check_dir(dirname, mode='staged')
except FileNotFoundError as err:
raise RuntimeError("""If you have trouble with clang-format, you can specify which version to use with
git config hooks.clangformat clang-format
The original error was: {}""".format(err))
if len(fails) > 0:
print('files need to be formatted:\n\t{}'.format('\n\t'.join(fails)) )
sys.exit(len(fails))