Search code examples
gitgithookspre-commit

How to prevent Git from skipping the pre-commit hook when using git commit -a?


We've got this repo for all our Puppet configs. I wrote the following to validate any changed .pp files and bail out of the commit if validation fails. The pre-commit script runs properly when triggered by git commit but git commit -a seems to skip the hook altogether. We use git commit -a exclusively so this behaviour is a bit of a deal breaker.

Am I doing something wrong? Alternatively, what could I do to duplicate git commit -a's behaviour in the pre-commit script?

#!/bin/bash
# check puppet files for errors before pushing to repo
for name in `git diff --name-only`; do
    if [[ $name == *.pp ]]; then
        path="$(pwd)/$name"
        puppet parser validate "$path"
        r=$?
        if [ $r -ne 0 ]; then exit 1; fi
    fi
done

Solution

  • Pre-commit hooks run on every commit, even when the -a option is specified. Confirm it by creating this script:

    #!/bin/bash
    echo "Running"
    

    Put the script into .git/hooks/pre-commit, editing and staging a file, and then:

    $ git commit -am "Commit"
    Running
    [master f042adf] Commit
     1 file changed, 1 insertion(+), 1 deletion(-)
    

    git diff by default diffs against unstaged changes. For example, edit README.md then:

    $ git diff --name-only
    README.md
    $ git add -A
    $ git diff --name-only
    $
    

    Once the changes had been staged, they no longer appeared in git diff.

    The -a option on git commit stages the changes before executing the pre-commit hook. For example, if we change our pre-commit hook to this:

    #!/bin/bash
    echo "Running"
    git diff --name-only
    

    Edit README.md again, then:

    $ git status
    On branch master
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
        modified:   README.md
    
    no changes added to commit (use "git add" and/or "git commit -a")
    $ .git/hooks/pre-commit
    Running
    README.md
    $ git commit -m "Commit"
    Running
    README.md
    On branch master
    Changes not staged for commit:
        modified:   README.md
    
    no changes added to commit
    

    The changes showed up in the diff output, but were not committed. But:

    $ git commit -am "Commit"
    Running
    [master a357465] Commit
     1 file changed, 1 insertion(+), 1 deletion(-)
    

    This time, using the -a option, the changes did not show in the diff output (because they were on the stage) but they were committed.

    So add the --cached option to the git diff command:

    #!/bin/bash
    echo "Running"
    git diff --name-only --cached
    

    Then, after making another change toREADME.md:

    $ git commit -am "Commit"
    Running
    README.md
    [master eaab554] Commit
     1 file changed, 1 insertion(+), 1 deletion(-)
    

    The answer is the --cached option on git diff.