I've been trying to make a pre-commit hook that checks if files in the index are formatted correctly. I've tried so many things already but I just can't get the grep to work correctly. This is my code right now:
for FILE in $(git diff --cached --name-only)
do
if [[ "$FILE" =~ \.(c|h|cpp|cc)$ ]]; then
exec C:/dev/uct/clang-format-15.0.3.exe --dry-run -Werror $FILE | grep -c "violations"
fi
done
I expect it to print the number of files that have formatting issues (I actually want to search for the string -Wclang-format-violations but I simplified it for testing).
If I &> the clang-format output to a file the violations correctly printed to that file.
exec C:/dev/uct/clang-format-15.0.3.exe --dry-run -Werror $FILE &> temp.txt
If I then run grep from the command line on that file it works.
grep -c violations temp.txt
I also tried grep on the file from the script but for some reason that didn't produce any output either (though I would prefer to not have to create a file).
exec C:/dev/uct/clang-format-15.0.3.exe --dry-run -Werror $FILE &> temp.txt
grep -c violations temp.txt
What am I doing wrong? I thought I would get this to work in half an hour at most.
The problem with grep
is that you're not redirecting stderr
, only stdout
. (In your redirection to a file, you are redirecting stderr as well, with >&
.) To capture stderr in the pipe, use |&
.
However you have a mismatch between what's staged to be committed and what you're looking at. In git, a file isn't staged to be committed, but its contents are. That means that you can stage - for example - part of a files contents to be committed. The file's contents in the working directory thus would not match what is staged.
Here's a concrete example:
echo "hello" > hello_world.txt
git add hello_world.txt
echo "world" >> hello_world.txt
At this point, the contents staged in the index (from the git add
on line 2) are hello
. But the file contains hello world
on disk.
git status
will show the file both as staged and modified:
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: hello_world.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello_world.txt
If you were to run git commit
now, only the hello
would be committed, not the file as it exists on disk.
That means that you want to look at the staged contents of the file -- which is what will actually be committed -- not just the file on disk. Otherwise you may get false positives or negatives.
To handle this case, you can pass what's staged to clang-format
using the git show
command. (git show :filename
will output the staged contents of filename
.)
Try this:
for FILE in $(git diff --cached --name-only)
do
if [[ "$FILE" =~ \.(c|h|cpp|cc)$ ]]; then
git cat-file --filters ":${FILE}" | C:/dev/uct/clang-format-15.0.3.exe --dry-run -Werror --assume-filename="${FILE}" |& grep -c grep -c "\-Wclang\-format\-violations"
fi
done