Skip to content

Instantly share code, notes, and snippets.

@rbanffy
Created October 4, 2021 09:13
Show Gist options
  • Save rbanffy/fccfcf8f5566d4d1e6c214efb15839a5 to your computer and use it in GitHub Desktop.
Save rbanffy/fccfcf8f5566d4d1e6c214efb15839a5 to your computer and use it in GitHub Desktop.
A better pre-commit hook
#!/bin/bash
# This script is meant to be symlinked into your project's .git/hooks
# or global ~/.githooks directory within projects you are working
# with.
# Note: linters should be configured by project-local config files, so
# we can avoid changing this script. It's fine to change it, but,
# remember if you do that, you're on your own. Python and JavaScript linters
# will ideally run from project-specific folders so they are aware of the
# context the files should be analyzed with.
# Set up the environment so that we exit (-e) immediately as soon as a
# command exits with a non-zero return value and (-o pipefail) any
# pipeline with internal non-zero returns will cause the script to
# error out. Do not set -u yet.
set -eo pipefail
# ANSI sequences, if we are running in a terminal. If not, leave empty.
if [ -t 1 ]; then
YELLOW="\033[33m"
RESET="\033[0m"
else
YELLOW=""
RESET=""
fi
# If there is a branch named "main", we assume it's the
# parent. Otherwise we assume the parent is "master".
if git branch --list | grep '^main$'; then
# There is a branch called main, so let's use it.
PARENT_BRANCH="main"
else
# We didn't find a main, so we assume base is master.
PARENT_BRANCH="master"
fi
echo "Using $PARENT_BRANCH as parent branch"
# Any unset (-u) variable from now on causes an error. We didn't do
# this before because we may want to manually override the parent
# branch from the command line. This override logic needs to be
# implemented
set -u
function lint_shell() {
# Reformat and ling a shell file. Since we do the same for added and
# modified files, we ignore $2.
shfmt -i 4 -w "$1"
shellcheck "$1"
}
function lint_python() {
# The two paths exist so that we can leave already existing files
# undisturbed while we enforce much stricter standards for new
# ones.
if [[ "$2" == "A" ]]; then # A indicates an added file.
# If this is a new file, we check and reformat the file more
# rigorously than we do existing files.
isort -faas "$1"
black -v "$1"
flake8 "$1"
else
# If the file is not being added in this branch, we just run a
# Flake 8 with default behavior.
flake8 "$1"
fi
}
function lint_go() {
# Lint a Go file. Since we do the same for added and mofified
# files, we ignore $2.
go fmt "$1"
}
function lint_markdown() {
# Lint a markdown file. Since we do the same for added and
# mofified files, we ignore $2.
lint-md "$1"
}
function lint_web() {
if [[ "$2" == "A" ]]; then # A indicates an added file.
# If this is a new file, we check and reformat the file more
# rigorously than we do existing files.
prettier --write "$1"
else
# If the file is not being added in this branch, we just check
# it with prettier.
prettier --check "$1"
fi
}
function lint_tf() {
# Always reformat Terraform files.
terraform fmt -check=true "$1"
}
function show_unprocessed() {
# Show a message in case a file wasn't processed.
echo -e "${YELLOW}File $1 ($2) was not processed.${RESET}"
}
function lint_file() {
# $1: the file we want to lint
# $2: "A" for added, any other value for modified
# Get the file extension.
extension=${1##*\.}
case $extension in
"sh")
lint_shell "$1" "$2"
;;
"py")
lint_python "$1" "$2"
;;
"go")
lint_go "$1" "$2"
;;
"md")
lint_markdown "$1" "$2"
;;
"js" | "css" | "html" | "ts" | "tsx")
lint_web "$1" "$2"
;;
"tf" | "tfvars")
lint_tf "$1" "$2"
;;
*)
# Extension is not a known one, but we can use file magic
type=$(file --mime-type -b "$1")
case $type in
"text/x-shellscript")
lint_shell "$1" "$2"
;;
*)
show_unprocessed "$1" "$type"
;;
esac
;;
esac
}
# Iterate over the files added on this branch (we computer the
# difference between the current branch and the parent branch we
# identified.
for file in $(git diff --name-only --diff-filter=Ad "$PARENT_BRANCH"); do
lint_file "$file" "A"
done
# Iterate over the files modified on this commit
for file in $(git diff --name-only --diff-filter=Md HEAD); do
lint_file "$file" "M"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment