Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions pre_commit_hooks/check_shebang_scripts_are_executable.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,34 @@
from __future__ import annotations

import argparse
import os
import shlex
import sys
from collections.abc import Sequence

from pre_commit_hooks.check_executables_have_shebangs import EXECUTABLE_VALUES
from pre_commit_hooks.check_executables_have_shebangs import git_ls_files
from pre_commit_hooks.check_executables_have_shebangs import has_shebang
from pre_commit_hooks.util import cmd_output


def check_shebangs(paths: list[str]) -> int:
# Cannot optimize on non-executability here if we intend this check to
# work on win32 -- and that's where problems caused by non-executability
# (elsewhere) are most likely to arise from.
return _check_git_filemode(paths)
fs_tracks_executable_bit = cmd_output(
'git', 'config', 'core.fileMode', retcode=None,
).strip()

# Fall back to Git's file mode when the filesystem cannot reliably
# track executable bits, as is commonly the case on Windows.
if fs_tracks_executable_bit == 'false': # pragma: win32 cover
return _check_git_filemode(paths)
else: # pragma: win32 no cover
retv = 0
for path in paths:
if has_shebang(path) and not os.access(path, os.X_OK):
_message(path)
retv = 1

return retv


def _check_git_filemode(paths: Sequence[str]) -> int:
Expand Down
25 changes: 25 additions & 0 deletions tests/check_shebang_scripts_are_executable_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import os
import sys

import pytest

Expand All @@ -9,6 +10,30 @@
from pre_commit_hooks.check_shebang_scripts_are_executable import main
from pre_commit_hooks.util import cmd_output

skip_win32 = pytest.mark.skipif(
sys.platform == 'win32',
reason="non-git checks aren't relevant on windows",
)


@skip_win32 # pragma: win32 no cover
@pytest.mark.parametrize(
('content', 'mode', 'expected'),
(
pytest.param('#!python', '+x', 0, id='shebang with executable'),
pytest.param('#!python', '-x', 1, id='shebang without executable'),
pytest.param('', '+x', 0, id='no shebang with executable'),
pytest.param('', '-x', 0, id='no shebang without executable'),
),
)
def test_non_git_executable_shebang(tmpdir, content, mode, expected):
with tmpdir.as_cwd():
path = tmpdir.join('path')
path.write(content)
cmd_output('chmod', mode, str(path))

assert main((str(path),)) == expected


def test_check_git_filemode_passing(tmpdir):
with tmpdir.as_cwd():
Expand Down
Loading