diff --git a/Makefile b/Makefile index 81d1858..5160e41 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ pre-commit=./venv/bin/pre-commit .PHONY: lint venv venv: - python3.7 -m venv venv + python3 -m venv venv $(pip) install pre-commit $(pre-commit) install -t commit-msg diff --git a/README.md b/README.md index f5649f8..a83e095 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,8 @@ in `.git/hooks` directory of your repository. no validation is done on JIRA refs. - if `COMMIT_VALIDATOR_ALLOW_TEMP` environment variable is not empty, no validation is done on `fixup!` and `squash!` commits. +- if `COMMIT_VALIDATOR_NO_REVERT_SHA1` environment variable is not empty, + no validation is done revert commits. ### Commit template @@ -276,6 +278,11 @@ jobs: - if `no_jira` is not empty, no validation is done on JIRA refs. - if `allow_temp` is not empty, no validation is done on `fixup!` and `squash!` commits. +- if `no_revert_sha1` is not empty, no validation is done on revert + commits. +- `jira_in_header` jira reference can be put in the commit header. +- `header_length` allow to override the max length of the header line. +- `jira_types` takes a space separated list `"feat fix"` as a parameter to override the default types requiring a jira ## Add pre-commit plugin @@ -303,9 +310,13 @@ Then run `pre-commit install --hook-type commit-msg` to install the ### Pre commit hook options -- if `no_jira` is set, no validation is done on JIRA refs. -- if `allow_temp` is set, no validation is done on `fixup!` and `squash!` +- if `no-jira` is set, no validation is done on JIRA refs. +- if `allow-temp` is set, no validation is done on `fixup!` and `squash!` commits. +- if `no-revert-sha1` is set, no validation is done on revert commits. +- if `--jira-in-header` jira reference can be put in the commit header. +- `--header-length` allow to override the max length of the header line. +- `--jira-types` takes a space separated list `"feat fix"` as a parameter to override the default types requiring a jira diff --git a/action.yml b/action.yml index d28f51a..34d981c 100644 --- a/action.yml +++ b/action.yml @@ -13,6 +13,18 @@ inputs: allow_temp: description: 'If not empty, no validation is done on `fixup!` and `squash!` commits.' required: false + no_revert_sha1: + description: 'If not empty, reverted sha1 commit is not mandatory in revert commit message.' + required: false + header_length: + description: 'If not empty, max header_length' + required: false + jira_types: + description: 'If not empty, space separated list of types that require Jira refs' + required: false + jira_in_header: + description: 'If not empty, allow for jira ref in header' + required: false runs: using: "composite" steps: @@ -31,4 +43,8 @@ runs: env: COMMIT_VALIDATOR_NO_JIRA: ${{ inputs.no_jira }} COMMIT_VALIDATOR_ALLOW_TEMP: ${{ inputs.allow_temp }} + COMMIT_VALIDATOR_NO_REVERT_SHA1: ${{ inputs.no_revert_sha1 }} + GLOBAL_JIRA_TYPES: ${{ inputs.jira_types }} + GLOBAL_MAX_LENGTH: ${{ inputs.header_length }} + GLOBAL_JIRA_IN_HEADER: ${{ inputs.jira_in_header }} shell: bash diff --git a/check.sh b/check.sh index 8b914b2..7f9ee15 100755 --- a/check.sh +++ b/check.sh @@ -20,4 +20,4 @@ do validate "$MESSAGE" done <<< $COMMITS -echo "All commits succesfully checked" +echo "All commits successfully checked" diff --git a/check_message.sh b/check_message.sh index 1691dd0..72478f5 100755 --- a/check_message.sh +++ b/check_message.sh @@ -2,19 +2,19 @@ set -eu -OPTIONS=$(getopt --long no-jira allow-temp -- "$@") -[ $? -eq 0 ] || { - echo "Incorrect options provided" - exit 1 -} +OPTIONS=$(getopt --long no-jira,allow-temp,jira-in-header,header-length:,jira-types: -- "$@") -COMMIT_VALIDATOR_ALLOW_TEMP= -COMMIT_VALIDATOR_NO_JIRA= +unset COMMIT_VALIDATOR_ALLOW_TEMP COMMIT_VALIDATOR_NO_JIRA COMMIT_VALIDATOR_NO_REVERT_SHA1 GLOBAL_JIRA_IN_HEADER GLOBAL_MAX_LENGTH GLOBAL_JIRA_TYPES +eval set -- $OPTIONS while true; do case "$1" in --no-jira ) COMMIT_VALIDATOR_NO_JIRA=1; shift ;; --allow-temp ) COMMIT_VALIDATOR_ALLOW_TEMP=1; shift ;; + --no-revert-sha1 ) COMMIT_VALIDATOR_NO_REVERT_SHA1=1; shift ;; + --jira-in-header ) GLOBAL_JIRA_IN_HEADER=1; shift ;; + --header-length ) GLOBAL_MAX_LENGTH="$2"; shift 2 ;; + --jira-types ) GLOBAL_JIRA_TYPES="$2"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac @@ -44,7 +44,10 @@ fi # print message so you don't lose it in case of errors # (in case you are not using `-m` option) -echo "Options: JIRA=$COMMIT_VALIDATOR_NO_JIRA, TEMP=$COMMIT_VALIDATOR_ALLOW_TEMP" +echo "Options: " +echo " JIRA=${COMMIT_VALIDATOR_NO_JIRA:-}" +echo " TEMP=${COMMIT_VALIDATOR_ALLOW_TEMP:-}" +echo " NO_REVERT_SHA1=${COMMIT_VALIDATOR_NO_REVERT_SHA1:-}" printf "checking commit message:\n\n#BEGIN#\n%s\n#END#\n\n" "$MESSAGE" validate "$MESSAGE" diff --git a/git-commit-template b/git-commit-template index bfe2a5a..126f1ee 100644 --- a/git-commit-template +++ b/git-commit-template @@ -22,8 +22,8 @@ type(scope): subject # kebab-case. ## Subject -# A brief but meaningfull description of the change. Here are some -# recommandation for writing your subject: +# A brief but meaningful description of the change. Here are some +# recommendation for writing your subject: # - use the imperative, present tense: "change" not "changed" nor "changes" # - don't capitalize first letter # - no "." (dot) at the end diff --git a/validator.bats b/validator.bats index 0cddb49..60c7bbc 100644 --- a/validator.bats +++ b/validator.bats @@ -97,15 +97,27 @@ ABC-1234" [[ $GLOBAL_FOOTER == "" ]] } +@test "structure: valid commit message with JIRA in header" { + COMMIT="feat(abc): ABC-1234 + +plop" + + GLOBAL_JIRA_IN_HEADER="allow" validate_overall_structure "$COMMIT" + [[ $GLOBAL_HEADER == "feat(abc): ABC-1234" ]] + [[ $GLOBAL_JIRA == "ABC-1234" ]] + [[ $GLOBAL_BODY == "plop"$'\n' ]] + [[ $GLOBAL_FOOTER == "" ]] +} + @test "structure: valid commit message with header and multiple JIRA" { COMMIT="plop plop -ABC-1234 DEF-1234" +ABC-1234 DE-1234" validate_overall_structure "$COMMIT" [[ $GLOBAL_HEADER == "plop plop" ]] [[ $GLOBAL_BODY == "" ]] - [[ $GLOBAL_JIRA == "ABC-1234 DEF-1234" ]] + [[ $GLOBAL_JIRA == "ABC-1234 DE-1234" ]] [[ $GLOBAL_FOOTER == "" ]] } @@ -328,6 +340,10 @@ BROKEN: [ "$status" -eq $ERROR_HEADER_LENGTH ] } +@test "header length cannot be more than 150 with spaces. overriden" { + GLOBAL_MAX_LENGTH=150 validate_header_length "012345678 012345678 012345678 012345678 012345678 012345678 012345678 1" +} + @test "header length can be 70" { run validate_header_length "0123456789012345678901234567890123456789012345678901234567890123456789" [ "$status" -eq 0 ] @@ -472,6 +488,8 @@ LUM-2345' run validate_revert "$MESSAGE" [[ "$status" -eq $ERROR_REVERT ]] + COMMIT_VALIDATOR_NO_REVERT_SHA1= run validate_revert "$MESSAGE" + [[ "$status" -eq $ERROR_REVERT ]] } @test "revert body with commit sha1 should be valid" { @@ -483,26 +501,37 @@ LUM-2345' run validate_revert "$MESSAGE" [[ "$status" -eq 0 ]] + COMMIT_VALIDATOR_NO_REVERT_SHA1= run validate_revert "$MESSAGE" + [[ "$status" -eq 0 ]] +} + +@test "revert body without sha1 should be valid with the flag" { + MESSAGE='rerer + +LUM-2345' + + COMMIT_VALIDATOR_NO_REVERT_SHA1=1 run validate_revert "$MESSAGE" + [[ "$status" -eq 0 ]] } @test "features and fixes commits need jira reference" { - [[ `need_jira "feat"` -eq 1 ]] - [[ `need_jira "fix"` -eq 1 ]] + need_jira "feat" + need_jira "fix" } @test "features and fixes commits need jira reference if env empty" { - [[ `COMMIT_VALIDATOR_NO_JIRA= need_jira "feat"` -eq 1 ]] - [[ `COMMIT_VALIDATOR_NO_JIRA= need_jira "fix"` -eq 1 ]] + COMMIT_VALIDATOR_NO_JIRA= need_jira "feat" + COMMIT_VALIDATOR_NO_JIRA= need_jira "fix" } @test "features and fixes commits don't need jira reference if env non empty" { - [[ `COMMIT_VALIDATOR_NO_JIRA=1 need_jira "feat"` -eq 0 ]] - [[ `COMMIT_VALIDATOR_NO_JIRA=1 need_jira "fix"` -eq 0 ]] + ! COMMIT_VALIDATOR_NO_JIRA=1 need_jira "feat" + ! COMMIT_VALIDATOR_NO_JIRA=1 need_jira "fix" } @test "other commits don't need jira reference" { - [[ `need_jira "docs"` -eq 0 ]] - [[ `need_jira "test"` -eq 0 ]] + ! need_jira "docs" + ! need_jira "test" } @test "feat without jira ref should be rejected" { @@ -520,6 +549,11 @@ LUM-2345' [[ "$status" -eq 0 ]] } +@test "feat with short jira ref should be validated" { + run validate_jira "feat" "AB-123" + [[ "$status" -eq 0 ]] +} + @test "overall validation invalid structure" { MESSAGE='plop plop' diff --git a/validator.sh b/validator.sh index 6145b0e..583e6f7 100644 --- a/validator.sh +++ b/validator.sh @@ -5,10 +5,11 @@ if [[ -v ZSH_NAME ]]; then fi readonly HEADER_PATTERN="^([^\(]+)\(([^\)]+)\): (.+)$" -readonly TYPE_PATTERN="^(feat|fix|docs|lint|refactor|test|chore)$" +readonly TYPE_PATTERN="^(feat|fix|docs|gen|lint|refactor|test|chore)$" readonly SCOPE_PATTERN="^([a-z][a-z0-9]*)(-[a-z0-9]+)*$" readonly SUBJECT_PATTERN="^([a-z0-9].*[^ ^\.])$" readonly JIRA_PATTERN="^([A-Z]{2,4}-[0-9]{1,6} ?)+$" +readonly JIRA_HEADER_PATTERN="^.*([A-Z]{3,4}-[0-9]{1,6}).*$" readonly BROKE_PATTERN="^BROKEN:$" readonly TRAILING_SPACE_PATTERN=" +$" readonly REVERT_HEADER_PATTERN="^[R|r]evert[: ].*$" @@ -31,6 +32,11 @@ GLOBAL_BODY="" GLOBAL_JIRA="" GLOBAL_FOOTER="" +# Overridable variables +GLOBAL_JIRA_TYPES="${GLOBAL_JIRA_TYPES:-feat fix}" +GLOBAL_MAX_LENGTH="${GLOBAL_MAX_LENGTH:-70}" +GLOBAL_JIRA_IN_HEADER="${GLOBAL_JIRA_IN_HEADER:-}" + GLOBAL_TYPE="" GLOBAL_SCOPE="" GLOBAL_SUBJECT="" @@ -52,6 +58,9 @@ validate_overall_structure() { if [[ $STATE -eq $WAITING_HEADER ]]; then GLOBAL_HEADER="$LINE" STATE="$WAITING_EMPTY" + if [[ -n "${GLOBAL_JIRA_IN_HEADER:-}" ]] && [[ $LINE =~ $JIRA_HEADER_PATTERN ]]; then + GLOBAL_JIRA=${BASH_REMATCH[1]} + fi elif [[ $STATE -eq $WAITING_EMPTY ]]; then if [[ $LINE != "" ]]; then @@ -141,12 +150,8 @@ validate_header() { validate_header_length() { local HEADER="$1" - local LENGTH - - LENGTH="$(echo -n "$HEADER" | wc -c)" - - if [[ $LENGTH -gt 70 ]]; then - echo -e "commit header length is more than 70 charaters" + if [[ ${#HEADER} -gt ${GLOBAL_MAX_LENGTH} ]]; then + echo -e "commit header length is more than ${GLOBAL_MAX_LENGTH} characters" exit $ERROR_HEADER_LENGTH fi } @@ -212,18 +217,14 @@ need_jira() { local TYPE=$1 if [[ ! -z "${COMMIT_VALIDATOR_NO_JIRA:-}" ]]; then - echo 0 + return 1 else - case $TYPE in - feat) - echo 1 - ;; - fix) - echo 1 - ;; - *) - echo 0 - esac + for type in ${GLOBAL_JIRA_TYPES}; do + if [[ "${TYPE}" == "${type}" ]]; then + return 0 + fi + done + return 1 fi } @@ -231,7 +232,9 @@ validate_jira() { local TYPE=$1 local JIRA=$2 - if [[ "$(need_jira "$TYPE")" -eq "1" && -z "${JIRA:-}" ]]; then + + + if need_jira "$TYPE" && [[ -z "${JIRA:-}" ]]; then echo -e "commits with type '${TYPE}' need to include a reference to a JIRA ticket, by adding the project prefix and the issue number to the commit message, this could be done easily with: git commit -m 'feat(widget): add a wonderful widget' -m LUM-1234" exit $ERROR_JIRA fi @@ -242,6 +245,10 @@ validate_revert() { local LINE="" local REVERTED_COMMIT="" + if [[ ! -z "${COMMIT_VALIDATOR_NO_REVERT_SHA1:-}" ]]; then + exit 0 + fi + while IFS= read -r LINE ; do if [[ $LINE =~ $REVERT_COMMIT_PATTERN ]]; then