gitpre-commit-hookvscode-devcontainer

Getting the error "fatal: cannot run .git/hooks/pre-commit: No such file or directory" when trying to execute git commit. The pre-commit file is there


My Situation

I've got a pre-commit hook installed within the .git/hooks directory of my project. However, whenever I try to perform a git commit -m "whatever" for a commit, I just get an error message saying the pre-commit file doesn't exist. The file definitely exists. If I remove the file, commits happen just fine again, but for this project I do need to have a pre-commit hook in place before committing to the repo.

My Environment

I'm running inside of a Visual Studio Code (VSCode) devcontainer. The devcontainer is using WSL-backed (Windows Subsystem for Linux) Docker with a container built from python:3.10-slim-bullseye.

Host OS

OS Name: Microsoft Windows 11 Enterprise
Version: 10.0.22621 Build 22621

VSCode

Version: 1.77.3 (system setup)
Commit: 704ed70d4fd1c6bd6342c436f1ede30d1cff4710
Date: 2023-04-12T09:16:02.548Z
Electron: 19.1.11
Chromium: 102.0.5005.196
Node.js: 16.14.2
V8: 10.2.154.26-electron.0
OS: Windows_NT x64 10.0.22621
Sandboxed: No

Devcontainer

$ uname -a
Linux 4fb27f5c92e0 5.10.102.1-microsoft-standard-WSL2 #1 SMP Wed Mar 2 00:30:59 UTC 2022 x86_64 GNU/Linux

$ cat /etc/issue           
Debian GNU/Linux 11 \n \l

$ git --version                                                           
git version 2.30.2

What I've tried

As an example, here is the output of running a git commit command. The commit will fail and not take place as a result of this error.

$ git commit -m "arbitrary git commit message"
fatal: cannot run .git/hooks/pre-commit: No such file or directory

I thought that maybe there was some issue within the pre-commit file that was causing the issue, so I stripped the file down to the bones, to just run an echo "test" command. But even that doesn't work (again, the file also definitely exists, so there is such a file or directory, Linux/Git!)

$ cat .git/hooks/pre-commit               
#!/bin/bash

echo "test"

Additionally, here are the permissions for the file:

-rwxrwxrwx 1 me root 816 Sep 28 17:22 .git/hooks/pre-commit

where I am me. This isn't a problem with the contents of the pre-commit file, as the pre-commit file itself is quite simple. This is an issue with Git being able to actually read/execute the file as I understand it.

I tried Googling around or looking at other SO posts about pre-commit hooks running into this issue, but most (all?) of those issues are issues within the pre-commit hook's code itself, not in running the pre-commit hook straight-up.

Notably, pre-commit works outside of devcontainer

If I were to use my host git and try to do a commit, the pre-commit hook will run (though it'll run into issues since my host doesn't have the software the pre-commit hook is attempting to use, that stuff is installed within the devcontainer). It's only an issue within the devcontainer.

What I expect to happen

I expect my pre-commit hook file to execute whenever I execute git commit.


Solution

  • Based on phd's comment, I was asked to try running the script directly, which was revealing of the problem.

    $ .git/hooks/pre-commit       
    zsh: .git/hooks/pre-commit: bad interpreter: /bin/bash^M: no such file or directory
    

    Additionally, I was asked to provide the output of cat -v of the file.

    $ cat -v .git/hooks/pre-commit
    #!/bin/bash^M
    ^M
    echo "test"^M
    

    Which also highlights the problem.

    The issue was with the End of Line character(s). In *nix (in this case, Linux), line-ending character(s) are the \n or Line Feed (LF) character. In Windows, the line-ending character(s) are \r\n, or the Carriage Return (CR) and Line Feed (LF) characters (oft abbreviated as CRLF).

    To fix the issue, I was able to use VScode to change the line ending characters by clicking on the line ending button on the bottom bar and changing it from CRLF to LF.

    Image showing the file now showing LF as the line ending characters rather than the CRLF which it had previously shown.

    Another way I could have done this is by using the command line to remove those pesky CR's. Something like the below, perhaps (untested) (also caveat if the script had \r in it for some other purpose like not at the end of a line it might ruin that):

    cat .git/hooks/pre-commit | tr -d '\r' > .git/hooks/pre-commit.tmp && mv .git/hooks/pre-commit.tmp .git/hooks/pre-commit