Skip to content

Instantly share code, notes, and snippets.

@marchelbling
Last active February 13, 2020 01:00
Show Gist options
  • Save marchelbling/7358077 to your computer and use it in GitHub Desktop.
Save marchelbling/7358077 to your computer and use it in GitHub Desktop.
git hooks

Git hooks for better pivotal integration:

  • pre-commit:
    • prevents commiting on a branch that is not suffixed by an issue number. Some special branches (develop, master) and hotfixes are not constrained
    • lints modified python files and exits upon non correctly formatted modules
    • other formats (js, scss) have pending code that is not currently called as the workflow for front code is not clear
  • prepare-commit-msg: prepend issue number to commit message if it's missing and if it is set in the branch name (see pre-commit hook)
  • post-checkout: [optional] automatically update submodules when checkouting a branch (requires to define the AUTO_SUBMODULE_UPDATE environment variable)

For easy setup:

current="$( pwd )" && cd "$( git rev-parse --show-cdup )" && if [ -d .hooks ]; then cd .hooks && git fetch origin && git reset --hard origin/master && cd ..; else git clone [email protected]:7358077.git .hooks; fi && if ([ ! -f .gitignore ] || ! grep .hooks .gitignore >/dev/null ); then echo -e ".hooks\n" >> .gitignore; fi && find .hooks -maxdepth 1 -type f | while read h; do chmod +x "${h}"; ln -s -f "../../${h}" "$(git rev-parse --git-dir)/hooks/$( basename ${h} )"; done && cd "${current}"
#!/bin/bash
function get_branch_name {
echo "$( git rev-parse --abbrev-ref HEAD )"
}
function has_valid_id {
local branch="$1"
echo "${branch}" | grep -e "_[-3a-zA-Z]*[0-9]\{1,\}$" 2>&1 1>/dev/null
return $?
}
function get_issue_id {
local branch="$1"
# see http://stackoverflow.com/questions/3162385/how-to-split-a-string-in-shell-and-get-the-last-field
echo "${branch}" | rev | cut -d'_' -f1 | rev | tr '[:lower:]' '[:upper:]'
}
function is_special_branch {
local branch="$1"
echo "${branch}" | grep -i "^\(hotfix\|release\|develop\|master\|HEAD\)" 1>/dev/null 2>&1
return $?
}
function is_not_autosquash_commit {
grep -v '\(fixup\|squash\)!' <<<"$1" >/dev/null
return $?
}
function get_extension {
echo "${1##*.}" | tr '[:upper:]' '[:lower:]'
}
#!/bin/bash
. "$( git rev-parse --show-toplevel )/.hooks/commons.sh"
. "$( git rev-parse --show-toplevel )/.hooks/lint_python.sh"
. "$( git rev-parse --show-toplevel )/.hooks/lint_js.sh"
. "$( git rev-parse --show-toplevel )/.hooks/lint_scss.sh"
function make_stash {
# Stash the current changes, so that we preserve them
git stash save -q --keep-index
}
function make_unstash {
# Drop the temporary modifications, and restore the stash
git reset -q --hard
git stash pop -q --index
}
function lint_files {
local current="$( pwd )"
cd "$( git rev-parse --show-toplevel )"
if git status --porcelain 2>/dev/null | grep '^.M '>/dev/null;
then
has_unstaged_changes=true
make_stash
else
has_unstaged_changes=false
fi
# Get the list of changed files from the index
lint="$( mktemp )"
local auto=true # automatically fix lint errors when possible
local status=0
while read line; do
local diffstatus="$( cut -d$'\t' -f1 <<<"${line}" )"
local filepath="$( cut -d$'\t' -f2 <<<"${line}" )"
local extension="$( get_extension "${filepath}" )"
case ${diffstatus} in
D)
echo "Skipping deleted file '${filepath}' linting..."
;;
*)
case ${extension} in
py | js | scss)
"lint_${extension}" "${filepath}" ;;
*)
continue ;; # skip unsupported extensions
esac
esac
done <<<"$( git diff --name-status --staged )"
if [ -s "${lint}" ] # if lint report is not empty there were errors
then
echo "There were linting errors (see ${lint}):"
cat "${lint}"
status=1
else
rm "${lint}"
fi
if ${has_unstaged_changes}; then
make_unstash
fi
cd "${current}"
return ${status}
}
#!/bin/bash
function assert_js_setup {
if ! eslint --version >/dev/null 2>&1;
then
echo 'Run `npm install [-g] eslint` to use this linter'
return 1
fi
return 0
}
function lint_js {
if ! assert_js_setup
then
return 0
fi
local js_file="$1"
local result=0
# Check the JS files
if [ -n "${js_file}" ]
then
local lint_errors=$( xargs eslint <<< "${js_file}" )
if [ -n "${lint_errors}" ]
then
[ ${result} -ne 0 ] && echo
result=1
echo '[JS] Some files seem to not be conforming to the coding style (eslint).'
echo "${lint_errors}" >> "${lint}"
fi
fi
return ${result}
}
#!/bin/bash
function assert_python_lint_setup {
# TODO: add a way to check version
if ! docformatter --version >/dev/null 2>&1;
then
echo 'Run `pip install --upgrade docformatter` to use this linter'
return 1
fi
if ! isort --version >/dev/null 2>&1;
then
echo 'Run `pip install --upgrade isort` to use this linter'
return 1
fi
if ! yapf --version >/dev/null 2>&1;
then
echo 'Run `pip install --upgrade yapf` to use this linter'
return 1
fi
if ! flake8 --version >/dev/null 2>&1;
then
echo 'Run `pip install --upgrade flake8 flake8-import-order` to use this linter'
return 1
fi
return 0
}
function lint_py {
if ! assert_python_lint_setup
then
return 0
fi
local module="$1"
docformatter -i --wrap-summaries 100 --wrap-descriptions 100 --no-blank "${module}" || true
isort --apply "${module}" || true
yapf -i "${module}" || true
if ! git diff --patch --exit-code "${module}" >> "${lint}";
then
git checkout "${module}"
fi
if ! flake8 "${module}" >>"${lint}";
then
return 1
fi
return 0
}
#!/bin/bash
function assert_scss_setup {
if ! stylelint --version >/dev/null 2>&1
then
echo 'Run `npm install -g stylelint` to use this linter'
return 1
fi
}
function lint_scss {
if ! assert_scss_setup
then
return 0
fi
local scss_files="$1"
local result=0
# Check the SCSS files
if [ -n "${scss_files}" ]
then
local lint_errors="$( xargs stylelint <<<"${scss_files}" )"
if [ -n "${lint_errors}" ]
then
[ ${result} -ne 0 ] && echo
result=1
echo '[SCSS] Some files seem to not be conforming to the coding style (SCSS-Lint).'
echo "${lint_errors}" >> "${lint}"
fi
fi
return ${result}
}
#!/bin/bash
if ${AUTO_SUBMODULE_UPDATE:-false};
then
git submodule update --init >/dev/null && echo "Submodules updated!"
fi
#!/bin/bash
. "$( git rev-parse --show-toplevel )/.hooks/commons.sh"
. "$( git rev-parse --show-toplevel )/.hooks/lint.sh"
function lint_branch_name {
local branch="$( get_branch_name )"
if ! is_special_branch "${branch}"
then
if ! has_valid_id "${branch}"
then
echo "Bad branch name: should be (feature/bug/chore/)detail_xxxx."
echo "Please use 'git branch -m ${branch} ${branch}_xxxxx'"
exit 1
fi
fi
}
lint_branch_name && \
lint_files
#!/bin/bash
. "$( git rev-parse --show-toplevel )/.hooks/commons.sh"
function get_issue_state {
local msg="$1"
# see http://stackoverflow.com/questions/3162385/how-to-split-a-string-in-shell-and-get-the-last-field
for state in "Fix" "Finish" "Deliver";
do
if echo "${msg}" | grep -i "^+${state}" 2>&1 1>/dev/null
then
# return state
if [ "${state}" == "Deliver" ];
then
echo "Delivers "
else
echo "${state}es "
fi
break
fi
done
}
function format_msg {
local msg="$1"
local id="$2"
local state="$3"
if [ -n "${state}" ]
then
# strip state
msg="$( echo "${msg}" | cut -d' ' -f2- )"
fi
# uppercase first letter
msg="$( tr '[:lower:]' '[:upper:]' <<< ${msg:0:1} )${msg:1}"
if [ -n "${id}" ] && ! echo "${msg}" | grep -F "#${id}" 2>&1 1>/dev/null
then
msg="[${state}#${id}] ${msg}"
fi
echo -e "${msg}\n\n"
}
commit_msg="$( cat $1 )"
if is_not_autosquash_commit "${commit_msg}";
then
branch="$( get_branch_name )"
if has_valid_id "${branch}"
then
issue_id="$( get_issue_id "${branch}" )"
fi
issue_state="$( get_issue_state "$1" )"
commit_msg="$( format_msg "${commit_msg}" "${issue_id}" "${issue_state}" )"
fi
echo "${commit_msg}" > "$1"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment