#!/usr/bin/env bash # This script checks if a commit message follows the specified format. # It's designed to be used as a 'commit-msg' git hook. # Format: # : # # [optional] # # [ref/close]: # --- Configuration --- # An array of allowed commit types ALLOWED_TYPES=(feat fix refactor perf style test docs build chore revert) # --- Colors for Output --- RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # The first argument to the hook is the path to the file containing the commit message COMMIT_MSG_FILE=$1 # --- Automated Commit Detection --- # Read the first line (header) for initial checks HEADER=$(head -n 1 "$COMMIT_MSG_FILE") echo 'Given commit message:' echo $HEADER # Check for Merge commits (covers 'git merge' and PR merges from GitHub/GitLab) # Examples: "Merge branch 'main' into ...", "Merge pull request #123 from ..." MERGE_PATTERN="^Merge (remote-tracking )?(branch|pull request|tag) .*" if [[ "$HEADER" =~ $MERGE_PATTERN ]]; then echo -e "${GREEN}Merge commit detected by message content. Skipping validation.${NC}" exit 0 fi # Check for Revert commits # Example: "Revert "feat: add new feature"" REVERT_PATTERN="^Revert \".*\"" if [[ "$HEADER" =~ $REVERT_PATTERN ]]; then echo -e "${GREEN}Revert commit detected by message content. Skipping validation.${NC}" exit 0 fi # Check for Cherry-pick commits (this pattern appears at the end of the message) # Example: "(cherry picked from commit deadbeef...)" # We use grep -q to search the whole file quietly. CHERRY_PICK_PATTERN="\(cherry picked from commit [a-f0-9]{7,40}\)" if grep -qE "$CHERRY_PICK_PATTERN" "$COMMIT_MSG_FILE"; then echo -e "${GREEN}Cherry-pick detected by message content. Skipping validation.${NC}" exit 0 fi # Check for Squash # Example: "Squash commits ..." SQUASH_PATTERN="^Squash .+" if [[ "$HEADER" =~ $SQUASH_PATTERN ]]; then echo -e "${GREEN}Squash commit detected by message content. Skipping validation.${NC}" exit 0 fi # --- Validation Functions --- # Function to print an error message and exit # Usage: error_exit "Your error message here" error_exit() { # >&2 redirects echo to stderr echo -e "${RED}ERROR: $1${NC}" >&2 echo -e "${YELLOW}Commit message format is incorrect. Aborting commit.${NC}" >&2 exit 1 } # --- Main Logic --- # 1. Read the header (first line) of the commit message HEADER=$(head -n 1 "$COMMIT_MSG_FILE") # 2. Validate the header format: : # Regex breakdown: # ^(type1|type2|...) - Starts with one of the allowed types # : - Followed by a literal colon # \s - Followed by a single space # .+ - Followed by one or more characters for the description # $ - End of the line TYPES_REGEX=$( IFS="|" echo "${ALLOWED_TYPES[*]}" ) HEADER_REGEX="^($TYPES_REGEX): .+$" if ! [[ "$HEADER" =~ $HEADER_REGEX ]]; then error_exit "Invalid header format.\n\nHeader must be in the format: : \nAllowed types: ${ALLOWED_TYPES[*]}\nExample: feat: add new user authentication feature" fi # Only validate footer if commit type is not chore TYPE=$(echo "$HEADER" | cut -d':' -f1) if [ "$TYPE" != "chore" ]; then # 3. Validate the footer (last line) of the commit message FOOTER=$(tail -n 1 "$COMMIT_MSG_FILE") # Regex breakdown: # ^(ref|close) - Starts with 'ref' or 'close' # : - Followed by a literal colon # \s - Followed by a single space # N25B- - Followed by the literal string 'N25B-' # [0-9]+ - Followed by one or more digits # $ - End of the line FOOTER_REGEX="^(ref|close): N25B-[0-9]+$" if ! [[ "$FOOTER" =~ $FOOTER_REGEX ]]; then error_exit "Invalid footer format.\n\nFooter must be in the format: [ref/close]: \nExample: ref: N25B-123" fi fi # 4. If the message has more than 2 lines, validate the separator # A blank line must exist between the header and the body. LINE_COUNT=$(wc -l <"$COMMIT_MSG_FILE" | xargs) # xargs trims whitespace # We only care if there is a body. Header + Footer = 2 lines. # Header + Blank Line + Body... + Footer > 2 lines. if [ "$LINE_COUNT" -gt 2 ]; then # Get the second line SECOND_LINE=$(sed -n '2p' "$COMMIT_MSG_FILE") # Check if the second line is NOT empty. If it's not, it's an error. if [ -n "$SECOND_LINE" ]; then error_exit "Missing blank line between header and body.\n\nThe second line of your commit message must be empty if a body is present." fi fi # If all checks pass, exit with success echo -e "${GREEN}Commit message is valid.${NC}" exit 0