204 lines
3.7 KiB
Bash
Executable File
204 lines
3.7 KiB
Bash
Executable File
#! /usr/bin/env bash
|
|
|
|
#
|
|
# Prelude - make bash behave sanely
|
|
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
|
|
#
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
#
|
|
# Globals
|
|
#
|
|
remember_git_start_and_end() {
|
|
HEAD="$(git rev-parse HEAD)"
|
|
ROOT="$(git log --pretty=format:%H | tail -n 1)"
|
|
}
|
|
|
|
#
|
|
# Utilities
|
|
#
|
|
die() {
|
|
local msg="$@"
|
|
[[ -z "${msg}" ]] || {
|
|
tput setaf 1 # red
|
|
tput bold
|
|
echo "${msg}"
|
|
tput sgr0 # reset
|
|
}
|
|
exit 1
|
|
}
|
|
|
|
error() {
|
|
local msg="$@"
|
|
echo -n '| '
|
|
tput setaf 1 # red
|
|
echo -n ' ✖'
|
|
tput sgr0 # reset
|
|
echo " ${msg}"
|
|
}
|
|
|
|
success() {
|
|
local msg="$@"
|
|
echo -n '| '
|
|
tput setaf 2 # green
|
|
echo -n ' ✓'
|
|
tput sgr0 # reset
|
|
echo " ${msg}"
|
|
}
|
|
|
|
log_commit() {
|
|
echo "○ $@"
|
|
}
|
|
|
|
# Check a command is present
|
|
ensure_command() {
|
|
local cmd="$1"
|
|
|
|
command -v "${cmd}" > /dev/null 2>&1 || {
|
|
die "Couldn't find required command: ${cmd}"
|
|
}
|
|
}
|
|
|
|
#
|
|
# Signal handling
|
|
#
|
|
cleanup() {
|
|
git reset --hard "${HEAD}" > /dev/null 2>&1
|
|
rm -f $$_commit_message
|
|
}
|
|
|
|
trap cleanup SIGHUP SIGINT SIGTERM
|
|
|
|
#
|
|
# Git helpers
|
|
#
|
|
|
|
# Go back one commit in history (first parent for merges)
|
|
step_back_one_commit() {
|
|
git reset --hard HEAD^ > /dev/null
|
|
log_commit "$(git rev-parse HEAD)"
|
|
}
|
|
|
|
current_commit_message() {
|
|
GIT_PAGER= git log --format=%B -n 1
|
|
}
|
|
|
|
current_commit_sha() {
|
|
git rev-parse HEAD
|
|
}
|
|
|
|
exit_if_not_git_repo() {
|
|
local gitroot="$(git rev-parse --show-toplevel 2> /dev/null)"
|
|
|
|
[[ "${gitroot}" == "" ]] && die 'Current directory is not in a repository'
|
|
return 0
|
|
}
|
|
|
|
#
|
|
# Checks
|
|
#
|
|
check_commit_message() {
|
|
local lineno=0
|
|
local length=0
|
|
local succeded=1
|
|
|
|
while read -r line ; do
|
|
let succeded=1
|
|
let lineno+=1
|
|
length=${#line}
|
|
|
|
[[ "${lineno}" -eq "1" ]] && {
|
|
[[ "${length}" -gt 50 ]] && {
|
|
error "Commit message: Subject line longer than 50 characters";
|
|
succeded=0
|
|
};
|
|
|
|
[[ ! "${line}" =~ ^[A-Z].*$ ]] && {
|
|
error "Commit message: Subject line not capitalised";
|
|
succeded=0
|
|
};
|
|
|
|
[[ "${line}" == *. ]] && {
|
|
error "Commit message: Subject line ended with a full stop";
|
|
succeded=0
|
|
};
|
|
}
|
|
|
|
[[ "${lineno}" -eq "2" ]] && [[ -n "${line}" ]] && {
|
|
error "Commit message: Subject line not separated by a blank line";
|
|
succeded=0;
|
|
};
|
|
|
|
[[ "${lineno}" -gt "1" ]] && [[ "${length}" -gt "72" ]] && {
|
|
error "Commit message: Body not wrapped at 72 characters";
|
|
succeded=0
|
|
};
|
|
done < $$_commit_message
|
|
|
|
[[ "${succeded}" -eq "1" ]] && success "Commit message"
|
|
return 0
|
|
}
|
|
|
|
run_checks() {
|
|
current_commit_message > $$_commit_message
|
|
check_commit_message
|
|
rm -f $$_commit_message
|
|
set +e
|
|
npm run lint > /dev/null 2>&1
|
|
if [[ "$?" -eq 0 ]]; then
|
|
success 'Lint'
|
|
else
|
|
error 'Lint: script did not exit successfully'
|
|
fi
|
|
npm test > /dev/null 2>&1
|
|
if [[ "$?" -eq 0 ]]; then
|
|
success 'Test'
|
|
else
|
|
error 'Test: script did not exit successfully'
|
|
fi
|
|
set -e
|
|
}
|
|
|
|
check_project() {
|
|
exit_if_not_git_repo
|
|
|
|
[[ -f './package.json' ]] || {
|
|
die 'This does not appear to be a node project'
|
|
}
|
|
|
|
[[ -z "$(json -f package.json 'scripts.lint')" ]] && {
|
|
die 'There is no lint script in the package.json'
|
|
}
|
|
|
|
[[ -z "$(json -f package.json 'scripts.test')" ]] && {
|
|
die 'There is no test script in the package.json'
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
traverse_history() {
|
|
while [[ "${ROOT}" != "$(current_commit_sha)" ]] ; do
|
|
run_checks
|
|
step_back_one_commit
|
|
done
|
|
}
|
|
|
|
#
|
|
# Main
|
|
#
|
|
ensure_command git
|
|
ensure_command tail
|
|
ensure_command npm
|
|
ensure_command json
|
|
check_project
|
|
remember_git_start_and_end
|
|
log_commit "HEAD: $(current_commit_sha)"
|
|
traverse_history
|
|
run_checks
|
|
log_commit "ROOT: $(current_commit_sha)"
|
|
cleanup
|
|
|
|
# vim: syntax=sh et ts=2 sts=2 sw=2
|