5

I'm trying to stop a git commit from continuing with a pre-commit hook. From reading the manual, it should stop when you return an exit code other than 0. I'm testing to see if the csscomb command returns an error, and if it does then break the loop and exit, but the git commit still continues on to entering a message via git commit (-a).

I've forked and modified the script below from https://github.com/filtercake/git-pre-commit-csscomb

#!/bin/bash

# pre-commit hook to comb staged .sass and .scss files
# save as .git/hooks/pre-commit
# make executable: chmod +x .git/hooks/pre-commit

# sources:

# http://www.hagenburger.net/BLOG/Using-Git-Commit-Hooks-to-Autocompile-Sass-to-CSS.html
# http://stackoverflow.com/questions/8470755/git-pre-commit-hook-add-file-into-index/8471009#8471009
# http://stackoverflow.com/questions/592620/how-to-check-if-a-program-exists-from-a-bash-script/677212#677212
# https://gist.github.com/openam/8406343

# check if sass/scss files are staged for commit

# diff-lines from http://stackoverflow.com/a/12179492/3019532
# takes the line
function diff-lines() {
    local path=
    local line=
    while read; do
        esc=$'\033'
        if [[ $REPLY =~ ---\ (a/)?.* ]]; then
            continue
        elif [[ $REPLY =~ \+\+\+\ (b/)?([^[:blank:]$esc]+).* ]]; then
            path=${BASH_REMATCH[2]}
        elif [[ $REPLY =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@.* ]]; then
            line=${BASH_REMATCH[2]}
        elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
            echo "Line $line:$REPLY"
            if [[ ${BASH_REMATCH[2]} != - ]]; then
                ((line++))
            fi
        fi
    done
}

if ! git diff --quiet --cached -- **/*.{sass,scss}; then

            echo ""
            echo "--> you checked in sass/scss files. lets comb them..."
            echo ""

            # check if csscomb is installed

            if hash csscomb 2>/dev/null; then

                echo -e "\033[1;32m--> found csscomb\033[0m"

                # TODO: check for csscomb update once a week

                # check if configfile exists

                if [ ! -f ./.csscomb.json ]; then
                    echo "--> no config file, using defaults"
                else
                    echo -e "\033[1;32m--> found .csscomb.json\033[0m"
                fi

                echo ""

                # Necessary check for initial commit
                against="4b825dc642cb6eb9a060e54bf8d69288fbee4904"
                git rev-parse --verify HEAD >/dev/null 2>&1 && against="HEAD"
                EXITCODE=0

                # (A)dded (C)opied or (M)odified
                # encapsulate the loop in {} http://stackoverflow.com/a/13665483/3019532
                git diff-index --cached --full-index --diff-filter=ACM $against | \
                {
                    while read -r line; do
                      FILE_PATH="$(echo ${line} |cut -d' ' -f6-)"
                      EXTENSION="${FILE_PATH##*.}"
                      # EXTENSION=${EXTENSION,,} # Convert to lowercase
                      REGEX=""

                      # Select discouraged words based on extension

                      if [ "${EXTENSION}" = "sass" ] || [ "${EXTENSION}" = "scss" ]; then
                        echo "--> staged sass/scss file: " $FILE_PATH
                        echo "--> combing..."
                        if csscomb $FILE_PATH; then
                            echo "--> adding combed file"
                            git add $FILE_PATH
                            echo "--> done"
                        else
                            echo "Check your CSS file for combing errors or alternatively add '-n' to your git commit to bypass this hook"
                            break
                            exit 1 # Should stop the git commit from continuing
                        fi
                      fi
                    done
                }
            else
                echo -e "\033[0;31m--> Oh noes, CSS Comb is not installed. Do:"
                echo "--> npm install csscomb -g"
                echo -e "\033[0m"
                echo "--> (you need to change and stage a sass/scss file again to see it work on the next commit...)"
                echo ""
            fi
fi

# Necessary check for initial commit
against="4b825dc642cb6eb9a060e54bf8d69288fbee4904"
git rev-parse --verify HEAD >/dev/null 2>&1 && against="HEAD"
EXITCODE=0

exit 0
Jarryd
  • 77
  • 1
  • 8
  • Have you checked that it is being run? E.g. by using an `exit 1`, `touch /tmp/testfile` (and see if the test file is created) or something right after the `#!/bin/bash` ? – Arc Jun 03 '15 at 01:53
  • @Archimedix Yep it's definitely being run; the `echo` just before the `break` and `exit 1` is being output to terminal. – Jarryd Jun 04 '15 at 02:16

1 Answers1

3
            git diff-index --cached --full-index --diff-filter=ACM $against | \
            {
                while read -r line; do

Here's your trouble. multi-command pipe stages are run in subshells, and the exit from that loop exits its subshell. Subshells have their own environments, too, so you can't pass variable settings along from them either.

while read -r; do
        if ! good $REPLY; then exit 1; fi
done <<EOD
$(git diff-index --cached --full-index --diff-filter=ACM $against)
EOD
jthill
  • 55,082
  • 5
  • 77
  • 137
  • Thank you @jthill. Would you please be able to provide your suggestion in context with the script? I'm trying to integrate but failing miserably. – Jarryd Jun 04 '15 at 02:18
  • It's just a matter of feeding the input as a here-doc instead of through a pipeline. It's arbitrary and baffling, it's just how it works. Any command in parentheses is in a subshell; any command that's part of a pipeline (with a | adjacent) is in a subshell, and subshells can't affect the invoking shell's environment (any more than the subshell invoked for a shell script). – jthill Jun 04 '15 at 02:42