Git pre-commit hook : changed/added files

GitHookPre Commit-HookPre Commit

Git Problem Overview


I am writing a pre-commit hook. I want to run php -l against all files with .php extension. However I am stuck.

I need to obtain a list of new/changed files that are staged. deleted files should be excluded.

I have tried using git diff and git ls-files, but I think I need a hand here.

Git Solutions


Solution 1 - Git

A slightly neater way of obtaining the same list is:

git diff --cached --name-only --diff-filter=ACM

This will return the list of files that need to be checked.

But just running php -l on your working copy may not be the right thing to do. If you are doing a partial commit i.e. just selecting a subset of the differences between your current working set and the HEAD for the commit, then the test will be run on your working set, but will be certifying a commit that has never existed on your disk.

To do it right you should extract the whole staged image to a temp area and perform the test there .

rm -rf $TEMPDIR
mkdir -p $TEMPDIR
git checkout-index --prefix=$TEMPDIR/ -af
git diff --cached --name-only --diff-filter=ACM | xargs -n 1 -I '{}' \bin\echo TEMPDIR/'{}' | grep \\.php | xargs -n 1 php -l

See [Building a better pre-commit hook for Git][1] for another implementation.

[1]: http://newartisans.com/2009/02/building-a-better-pre-commit-hook-for-git/ "Building a better pre-commit hook for Git"

Solution 2 - Git

git diff --cached --name-status will show a summary of what's staged, so you can easily exclude removed files, e.g.:

M       wt-status.c
D       wt-status.h

This indicates that wt-status.c was modified and wt-status.h was removed in the staging area (index). So, to check only files that weren't removed:

steve@arise:~/src/git <master>$ git diff --cached --name-status | awk '$1 != "D" { print $2 }'
wt-status.c
wt-status.h

You will have to jump through extra hoops to deal with filenames with spaces in though (-z option to git diff and some more interesting parsing)

Solution 3 - Git

None of the answers here support filenames with spaces. The best way for that is to add the -z flag in combination with xargs -0

git diff --cached --name-only --diff-filter=ACM -z | xargs -0 ...

This is what is given by git in built-in samples (see .git/hooks/pre-commit.sample)

Solution 4 - Git

Here is what I use for my Perl checks:

#!/bin/bash

while read st file; do
    # skip deleted files
    if [ "$st" == 'D' ]; then continue; fi
    
    # do a check only on the perl files
    if [[ "$file" =~ "(.pm|.pl)$" ]] && ! perl -c "$file"; then
        echo "Perl syntax check failed for file: $file"
        exit 1
    fi
done < <(git diff --cached --name-status)

for PHP it will look like this:

#!/bin/bash

while read st file; do
    # skip deleted files
    if [ "$st" == 'D' ]; then continue; fi
    # do a check only on the php files
    if [[ "$file" =~ ".php$" ]] && ! php -l "$file"; then
        echo "PHP syntax check failed for file: $file"
        exit 1
    fi
done < <(git diff --cached --name-status)

Solution 5 - Git

git diff --cached is not sufficient if the commit call was specified with the -a flag, and there is no way to determine if that flag has been thrown in the hook. It would help if the arguments to commit should be available to the hook for examination.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionigorwView Question on Stackoverflow
Solution 1 - GitLarryHView Answer on Stackoverflow
Solution 2 - GitaraqnidView Answer on Stackoverflow
Solution 3 - GiteddygeekView Answer on Stackoverflow
Solution 4 - GitMarian HackMan MarinovView Answer on Stackoverflow
Solution 5 - GitmpersicoView Answer on Stackoverflow