timetowonder
timetowonder

Reputation: 5421

How can I make a Git pre-commit code check?

Is it even possible to accomplish this with Git? :)

I want this:

Sometimes I switch one variable in my code to true (localMode = true;) for my own debugging purposes. But this should never be committed. I should only commit code with the variable set to false. And of course sometimes I forget to make this change. Is it possible for Git to somehow stop or warn me if I am about to commit the 'wrong' code?


I ended up with the following shell script:

#!/bin/bash
git diff --cached --name-only | while read FILE; do
if [[ $(echo "$FILE" | grep -E "^.+main\-controller\.js$") ]]; then
    content=$(<"$FILE")
    if [[ $(echo "$content" | grep -E "rootScope\.localMode = true") ]]; then
        echo -e "\e[1;31m\tCommit contains localMode set to true.\e[0m" >&2
        exit 1
    fi
fi
done

Upvotes: 11

Views: 27696

Answers (4)

Maxime Brehin
Maxime Brehin

Reputation: 387

There are plenty of resources now to check contents using a Git pre-commit hook.

The most "famous" is probably Pre-commit.

Git hooks are sometimes hard to maintain and share (even if Git 2.9 introduced the core.hooksPath configuration that makes it easier).

I mostly work with JavaScript, and I found a popular module named Husky that manages shareable hooks through a project. I like it because it's integrated, shared, and configured in my projects through my package.json file.

I also tried to find a complementary module to check my contents before committing, but I didn't find anything satisfying. I wanted something similar with a shared configuration (in package.json) like this:

"precommit-checks": [
  {
    "filter": "\\.js$",
    "nonBlocking": "true",
    "message": "You’ve got leftover `console.log`",
    "regex": "console\\.log"
  },
  {
    "message": "You’ve got leftover conflict markers",
    "regex": "/^[<>|=]{4,}/m"
  },
  {
    "message": "You have unfinished devs",
    "nonBlocking": "true",
    "regex": "(?:FIXME|TODO)"
  }
]

I finally made my own: git-precommit-checks. You can try it if you want, otherwise you can still look for alternatives (especially if you don't work with JavaScript).

If you still want to check your contents using a Bash script, you can try a more generic approach and loop over an array of searched patterns that should stop your commit.

#! /bin/bash
# If you encounter any error like `declare: -A: invalid option`
# then you'll have to upgrade bash version to v4.
# For Mac OS, see http://clubmate.fi/upgrade-to-bash-4-in-mac-os-x/

# Hash using its key as a search Regex, and its value as associated error message
declare -A PATTERNS;
PATTERNS['^[<>|=]{4,}']="You've got leftover conflict markers";
PATTERNS['focus:\s*true']="You've got a focused spec";

# Declare empty errors array
declare -a errors;

# Loop over staged files and check for any specific pattern listed in PATTERNS keys
# Filter only added (A), copied (C), modified (M) files
for file in $(git diff --staged --name-only --diff-filter=ACM --no-color --unified=0); do
  for elem in ${!PATTERNS[*]} ; do
    { git show :0:"$file" | grep -Eq ${elem}; } || continue;
    errors+=("${PATTERNS[${elem}]} in ${file}…");
  done
done

# Print errors
# author=$(git config --get user.name)
for error in "${errors[@]}"; do
  echo -e "\033[1;31m${error}\033[0m"
  # Mac OS only: use auditable speech
  # which -s say && say -v Samantha -r 250 "$author $error"
done

# If there is any error, then stop commit creation
if ! [ ${#errors[@]} -eq 0 ]; then
  exit 1
fi

This is an update for people who want to find alternatives to previous solutions.

Upvotes: 3

sangheestyle
sangheestyle

Reputation: 1077

Use How Git hooks made me a better (and more lovable) developer.

I think you can just add some regular expression to detect localMode = true into the sample code at the page.

Upvotes: 6

dcastro
dcastro

Reputation: 68660

Yes, you can use a pre-commit hook.

Just drop a shell script named pre-commit (without any extension) inside your ".git/hooks" folder with the logic to check your variable and either:

  • change it to false and continue with the commit or
  • print a message telling the user to correct the value manually, and exit with a non-zero code to abort the commit

The hooks folder should contain a few samples, such as "pre-commit.sample", which you might find helpful.

From the documentation:

The pre-commit hook is run first, before you even type in a commit message. It’s used to inspect the snapshot that’s about to be committed, to see if you’ve forgotten something, to make sure tests run, or to examine whatever you need to inspect in the code. Exiting non-zero from this hook aborts the commit, although you can bypass it with git commit --no-verify. You can do things like check for code style (run lint or something equivalent), check for trailing whitespace (the default hook does exactly this), or check for appropriate documentation on new methods.

Upvotes: 6

kubanczyk
kubanczyk

Reputation: 5941

Here is my take based on an earlier answer:

#! /bin/bash
#
# This is a git hook. It exits with non-zero status and thus aborts
# a running `git commit` if the processed files contain undesirable PATTERNS.
#
# To enable this hook, rename this file to .git/hooks/pre-commit and run:
#   chmod a+x .git/hooks/pre-commit
# 
# To make it global:
#   git config --global core.hooksPath ~/git-central-hooks
#
# Source: https://stackoverflow.com/questions/26992576/how-to-make-a-git-pre-commit-code-check
# 
# Known problem:
# If you encounter error `declare: -A: invalid option` or similar upgrade bash 
# version to v4. For Mac OS, see http://clubmate.fi/upgrade-to-bash-4-in-mac-os-x/
#

# Declare empty arrays
declare -A PATTERNS
declare -a errors

# Customize it:  ['your grep pattern'] ===> "Error message when found"
PATTERNS['^[<>|=]{4,}']="You've got leftover CONFLICT markers"
PATTERNS['FIXME']="You've got FIXME hanging (consider changing to TODO)"

while read file 
do
  for elem in ${!PATTERNS[*]}
  do
    if git show :0:"$file" | grep -Eq "$elem"
    then
        errors+=( "${PATTERNS[${elem}]} in file '$file'" )
    fi
  done
# The post-loop expression generates only filenames added (A) or modified (M)
done < <( git diff --staged --name-only --diff-filter=AM --no-color --unified=0 )

# Print errors
for error in "${errors[@]}"
do
  echo -e "\033[1;31m${error}\033[0m"
  # Mac OS only: use auditable speech
  # author=$(git config --get user.name)
  # which -s say && say -v Samantha -r 250 "$author $error"
done

# Fail if there is an error
if [[ ${#errors[@]} -ne 0 ]]
then
  exit 1
fi

Upvotes: 2

Related Questions