github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/githooks/commit-msg (about) 1 #!/usr/bin/env bash 2 # 3 # -u: we want the variables to be properly assigned. 4 # -o pipefail: we want to test the result of pipes. 5 # No -e because we have failing commands and that's OK. 6 set -uo pipefail 7 8 grep=${GREP:-grep} 9 awk=${AWK:-awk} 10 sed=${SED:-sed} 11 12 # Support both terminfo and termcap. 13 # We want to check whether stderr is a terminal (to enable 14 # printing colors). We want this test to work even if stdin is 15 # redirected (e.g. when run from git commit) or stdout is 16 # redirected (e.g. in the var assignments below). 17 red=$([ -t 2 ] && { tput setaf 1 || tput AF 1; } 2>/dev/null) 18 reset=$([ -t 2 ] && { tput sgr0 || tput me; } 2>/dev/null) 19 warn() { 20 echo >&2 21 echo "${red}$*${reset}" >&2 22 echo >&2 23 } 24 25 if ! test -s "$1"; then 26 warn "commit message is empty!" 27 # nothing to check further. 28 exit 0 29 fi 30 31 # First lint test: give a friendly reminder on long lines. 32 33 longlines=$($awk '/^# --* >8 --*/ {exit} /^[^#]/ { if (length() >= 80) print }' <"$1") 34 if test -n "$longlines"; then 35 warn "Long line(s) detected:" 36 echo "$longlines" | $sed -e 's/^/ /g' >&2 37 echo >&2 38 echo "Please amend and wrap long lines." >&2 39 echo "(Long lines are OK if inside a preformatted block or a table.)" >&2 40 echo >&2 41 fi 42 43 # Lint the release notes. 44 # 45 # It's OK to have multiple entries per commit 46 # (e.g. changes in different categories). But that means we need to 47 # extract them separately for analysis. 48 # 49 saveIFS=$IFS 50 IFS=' 51 ' 52 notes=($($grep -iE '^release note' "$1")) 53 54 # Set this to 1 to require a release justification note. 55 require_justification=0 56 justification=($($grep -iE '^release justification: \S+' "$1")) 57 58 IFS=$saveIFS 59 60 informed= 61 inform() { 62 if [ -z "$informed" ]; then 63 info=${rnote:0:40} 64 if [ "$info" != "$rnote" ]; then info="$info..."; fi 65 warn "Warning: malformed release note entry: $info" 66 fi 67 informed=1 68 } 69 hint() { 70 inform 71 echo "- $*" >&2 72 } 73 74 if [ "$require_justification" = 1 -a 0 = ${#justification[*]} ]; then 75 warn "No release justification specified." 76 echo "Try: 'Release justification: This commit is safe for this release because..." >&2 77 echo >&2 78 fi 79 80 if [ 0 = ${#notes[*]} ]; then 81 warn "No release note specified." 82 echo "Try: 'Release note (...): ...'" >&2 83 echo >&2 84 else 85 for rnote in "${notes[@]}"; do 86 informed= 87 if echo "$rnote" | $grep -qiE '^release note' && ! echo "$rnote" | $grep -qE '^Release note'; then 88 hint "case matters! Try '${rnote:0:12}' -> 'Release note'" 89 fi 90 if echo "$rnote" | $grep -qiE '^release notes'; then 91 hint "singular please. Use multiple entries if there are multiple notes. Try '${rnote:0:13}' -> 'Release note'" 92 fi 93 if echo "$rnote" | $grep -qiE '^release notes? *: *\('; then 94 entered=$(echo "$rnote" | cut -d')' -f1)\); entered=${entered:0:40} 95 cat=$(echo "$rnote" | cut -d'(' -f2 | cut -d')' -f1) 96 hint "place the category before the colon. Try '$entered' -> 'Release note ($cat):'" 97 fi 98 if echo "$rnote" | $grep -qiE '^release notes? *: *[^ ]* *:'; then 99 cat=$(echo "$rnote" | $sed -e 's/^[^:]*: *//g;s/ *:.*$//g') 100 entered=$(echo "$rnote" | cut -d: -f1-2) 101 hint "place category within parentheses. Try '$entered:' -> 'Release note ($cat):'" 102 fi 103 if echo "$rnote" | $grep -qiE '^release notes?[^:]*: *none' && ! echo "$rnote" | $grep -qE '^Release note: [nN]one'; then 104 entered=$(echo "$rnote" | $sed -e 's/none.*/none/ig') 105 hint "for no release notes use 'none' with no category. Try '$entered' -> 'Release note: none'" 106 fi 107 if ! echo "$rnote" | $grep -qiE '^release notes? *: *none' && echo "$rnote" | $grep -qiE '^release notes? *: *[^( ]'; then 108 entered=$(echo "$rnote" | cut -d: -f2-); entered=${entered:0:40} 109 hint "category missing (can only be omitted if note is 'none'). Try 'Release note (category):$entered'" 110 fi 111 if echo "$rnote" | $grep -qiE '^release notes?[^:]*:([^ ]| *)' || \ 112 echo "$rnote" | $grep -qiE '^release notes?(\(| +\()' || \ 113 echo "$rnote" | $grep -qiE '^release notes? *\( +' || \ 114 echo "$rnote" | $grep -qiE '^release notes? *\( *[^)]* +\)' ; then 115 entered=${rnote:0:40} 116 body=$(echo "$rnote"| cut -d: -f2-|cut -c1-40|$sed -e 's/^ *//g') 117 cat=$(echo "$rnote" | cut -d'(' -f2 |cut -d')' -f1|$sed -e 's/^ *//g;s/ *$//g') 118 if test -z "$cat"; then cat=...; fi 119 hint "some space problem. Try '$entered' -> 'Release note ($cat): $body'" 120 fi 121 # Now prune the category 122 if echo "$rnote" | $grep -qiE '^release notes? *\([^)]*\)'; then 123 cat=$(echo "$rnote" | cut -d'(' -f2|cut -d')' -f1|$sed -e 's/^ *//g;s/ *$//g') 124 lower=$(echo "$cat"|tr A-Z a-z) 125 if [ "$cat" != "$lower" ]; then 126 hint "categories in lowercase. Try 'Release note ($cat)' -> 'Release note ($lower)'" 127 fi 128 if echo "$rnote" | $grep -qiE '^release notes? *\([^)/]*/'; then 129 repl=$(echo "$lower"|$sed -e 's| */ *|, |g') 130 hint "use commas to separate categories. Try '($lower)' -> '($repl)'" 131 lower=$repl 132 fi 133 saveIFS=$IFS 134 IFS=' 135 ' 136 cats=($(echo "$lower" | tr ',' '\n' | $sed -e 's/^ *//g')) 137 IFS=$saveIFS 138 for lcat in "${cats[@]}"; do 139 case $lcat in 140 "cli change") ;; 141 "sql change") ;; 142 "admin ui change") ;; 143 "general change") ;; 144 "build change") ;; 145 "enterprise change") ;; 146 "backward-incompatible change") ;; 147 "performance improvement") ;; 148 "bug fix") ;; 149 "security update") ;; 150 bugfix) 151 hint "Try 'Release note (bugfix)' -> 'Release note (bug fix)'" ;; 152 security*) 153 hint "Try 'Release note ($lcat)' -> 'Release note (security update)'" ;; 154 sql*) 155 hint "Try 'Release note ($lcat)' -> 'Release note (sql change)'" ;; 156 "backwards-incompatible"*|"backward incompatible"*) 157 hint "Try '$lcat' -> 'backward-incompatible change'" ;; 158 *) hint "unknown category '$lcat'" ;; 159 esac 160 done 161 fi 162 # Do some linting on the message itself. 163 msg=$(echo "$rnote" | cut -d':' -f1-) 164 if echo "$msg" | $grep -qiE ' *([cC]loses?|[fF]ix(es)?) *#[0-9]+\.? *'; then 165 hint "don't just close or fix. Explain." 166 fi 167 done 168 if test -n "$informed"; then echo >&2; fi 169 fi