Repository: pre-commit/pre-commit
Pull request #2065 (2021-09-27 23:36:34)

port hook template to bash
this avoids some version-specific code in python

this also makes the bootstrap script slightly more portable

closes #2045 


Patch:
diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py
index 73c8d60..7974423 100644
--- a/pre_commit/commands/install_uninstall.py
+++ b/pre_commit/commands/install_uninstall.py
@@ -1,6 +1,7 @@
 import itertools
 import logging
 import os.path
+import shlex
 import shutil
 import sys
 from typing import Optional
@@ -100,19 +101,17 @@ def _install_hook_script(
     args = ['hook-impl', f'--config={config_file}', f'--hook-type={hook_type}']
     if skip_on_missing_config:
         args.append('--skip-on-missing-config')
-    params = {'INSTALL_PYTHON': sys.executable, 'ARGS': args}
 
     with open(hook_path, 'w') as hook_file:
         contents = resource_text('hook-tmpl')
         before, rest = contents.split(TEMPLATE_START)
-        to_template, after = rest.split(TEMPLATE_END)
-
-        before = before.replace('#!/usr/bin/env python3', shebang())
+        _, after = rest.split(TEMPLATE_END)
 
         hook_file.write(before + TEMPLATE_START)
-        for line in to_template.splitlines():
-            var = line.split()[0]
-            hook_file.write(f'{var} = {params[var]!r}\n')
+        hook_file.write(f'INSTALL_PYTHON={shlex.quote(sys.executable)}\n')
+        # TODO: python3.8+: shlex.join
+        args_s = ' '.join(shlex.quote(part) for part in args)
+        hook_file.write(f'ARGS=({args_s})\n')
         hook_file.write(TEMPLATE_END + after)
     make_executable(hook_path)
 
diff --git a/pre_commit/resources/hook-tmpl b/pre_commit/resources/hook-tmpl
index 299144e..1dd66a2 100755
--- a/pre_commit/resources/hook-tmpl
+++ b/pre_commit/resources/hook-tmpl
@@ -1,44 +1,20 @@
-#!/usr/bin/env python3
+#!/usr/bin/env bash
 # File generated by pre-commit: https://pre-commit.com
 # ID: 138fd403232d2ddd5efb44317e38bf03
-import os
-import sys
-
-# we try our best, but the shebang of this script is difficult to determine:
-# - macos doesn't ship with python3
-# - windows executables are almost always `python.exe`
-# therefore we continue to support python2 for this small script
-if sys.version_info < (3, 3):
-    from distutils.spawn import find_executable as which
-else:
-    from shutil import which
-
-# work around https://github.com/Homebrew/homebrew-core/issues/30445
-os.environ.pop('__PYVENV_LAUNCHER__', None)
 
 # start templated
-INSTALL_PYTHON = ''
-ARGS = ['hook-impl']
+INSTALL_PYTHON=''
+ARGS=(hook-impl)
 # end templated
-ARGS.extend(('--hook-dir', os.path.realpath(os.path.dirname(__file__))))
-ARGS.append('--')
-ARGS.extend(sys.argv[1:])
-
-DNE = '`pre-commit` not found.  Did you forget to activate your virtualenv?'
-if os.access(INSTALL_PYTHON, os.X_OK):
-    CMD = [INSTALL_PYTHON, '-mpre_commit']
-elif which('pre-commit'):
-    CMD = ['pre-commit']
-else:
-    raise SystemExit(DNE)
 
-CMD.extend(ARGS)
-if sys.platform == 'win32':  # https://bugs.python.org/issue19124
-    import subprocess
+HERE="$(cd "$(dirname "$0")" && pwd)"
+ARGS+=(--hook-dir "$HERE" -- "$@")
 
-    if sys.version_info < (3, 7):  # https://bugs.python.org/issue25942
-        raise SystemExit(subprocess.Popen(CMD).wait())
-    else:
-        raise SystemExit(subprocess.call(CMD))
-else:
-    os.execvp(CMD[0], CMD)
+if [ -x "$INSTALL_PYTHON" ]; then
+    exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
+elif command -v pre-commit; then
+    exec pre-commit "${ARGS[@]}"
+else
+   echo '`pre-commit` not found.  Did you forget to activate your virtualenv?' 1>&2
+   exit 1
+fi


Test patch:
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
index 3c07124..8339903 100644
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -278,11 +278,7 @@ def test_environment_not_sourced(tempdir_factory, store):
         hook = os.path.join(path, '.git/hooks/pre-commit')
         with open(hook) as f:
             src = f.read()
-        src = re.sub(
-            '\nINSTALL_PYTHON =.*\n',
-            '\nINSTALL_PYTHON = "/dne"\n',
-            src,
-        )
+        src = re.sub('\nINSTALL_PYTHON=.*\n', '\nINSTALL_PYTHON="/dne"\n', src)
         with open(hook, 'w') as f:
             f.write(src)