DEV Community

Marko V
Marko V

Posted on • Edited on

Git Hooks, Git Gud.

Having worked with git hooks today, I encountered a very strange set of circumstances. Examples I found on blog-posts on the net wanted you to name the file according to the script execution it was supposed to use as well as using the "shebang" on the first line. For instance if it was a shell script, it was supposed to have .sh extension as well as the #!/bin/sh on the first line. However, this proved not to be true.

Now I tried a few different versions, where people mentioned that I needed to have the Windows path to the sh.exe that git uses. Git for Windows uses a scaled down port of the various shell types so they dont support all the different kinds of script binaries/commands that you might be used to. So when it didn't work at first, I presumed it was due to that. That bash.exe didnt support all the functionality that the script required. I went through a few variations and ended up with this eventually (https://gist.github.com/luuuis/e41fd71134ce88ac5e9359cbdbfb6273) however, later I found this to not be the true cause of the script not being run/executed.

The true combination of working circumstances.

First of all. There's a discrepancy between how VS Code and VS 2019 does it. VS Code executes the githook in the context of whatever you provide it. VS 2019 has it's own bash-shell apparently, because it complained that the script didnt have the #!/bin/sh shebang but instead had the windows shebang (#!C:/Program\ Files/Git/bin/bash.exe). So VS Code is more lenient on the requirement on the shebang, as long as it points to a valid binary or link thereof for the script context to execute. Also it seems that VS2019 has a hidden limitation then to sh-scripts.

The correct combination that worked for both VS Code and VS 2019 was to NOT have an extension (pre-commit ONLY) and then the #!/bin/sh shebang only on the first line. So close to what all the various documentations were pointing to. Just that the comment-noise and "helpful" issue-comments, though a year old or so, strangely enough didnt need it? Or it was supported at the time, but has since been removed in favour of making it cross-platform(?) maybe.... just guessing here and trying to find an explanation to this strangeness.

Adding to the functionality

After having run through all of this, and confirmed that it worked. I even tried extending the script to use grep-filtering to exclude test files. Our test-files are using a more modern approach and framework meaning it doesnt have the same strict requirements as the webapplication. This meant that applying our .eslintrc indiscriminatorily on the committed files, will make it error-out on files that necessarily dont need to be handled as strictly. So I tried using grep -v 'test.js' to exclude all JS files that had test.js extension on them. Now that didnt happen. The files went through anyway. So for now, I jot that down to limitation in the ported sh.exe and just be happy that it's even executing the script.

Now as for sharing the script and enforcing it on the team meant that I needed to move the scripts outside of the .git folder that isn't versioned. So I created a .githooks folder in the solution directory. Then I also had to add git config core.hooksPath .githooks as a pre-build step in the project that needed to run this. In doing so, together with syncronising the .githooks folder, now the application will always enforce that folder for githooks on build time. It will prevent developers from accidentally and/or pre-maturely commit code that is introducing more errors to the application structure and legacy files that are being edited, have to be linted. If a developer strictly needs to circumvent this, they can always commit with --no-verify flag in the commandline. There can be any number of circumstances requiring to do this, but it should not be the norm.

We have to get our linting issues down.

The final changes;

File: ./.githooks/pre-commit

#!/bin/sh 
set -uo pipefail 
IFS=$'\n\t' 

git diff --diff-filter=d --cached --name-only -z -- '*.js' \ 
  grep -v '.test.js' | xargs -0 -I % sh -c 'git show ":%" | ./MyProject/node_modules/.bin/eslint --stdin --stdin-filename "%";' 
eslint_exit=$? 

if [ ${eslint_exit} -eq 0 ]; then 
 echo "βœ“ ESLint passed" 
else 
 echo "✘ ESLint failed!" 1>&2 
 exit ${eslint_exit} 
fi 

Enter fullscreen mode Exit fullscreen mode

File: ./MyProject/MyProject.csproj

<?xml version="1.0" encoding="utf-8"?> 
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">  
... 
  <PropertyGroup>   
    <PreBuildEvent>git config core.hooksPath .githooks</PreBuildEvent>   
  </PropertyGroup> 
... 
</Project> 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)