#!/usr/bin/env bash
#
# commitlint [GITRANGE|FILE]

# Check unpushed commits, or commits in the specified range, or a
# commit message in FILE, for compliance with hledger's conventions
# (see https://hledger.org/COMMITS.html or the code below).
# If the argument contains - or .. or ^! it's a range, otherwise
# a file containing a proposed commit message. 
# Run interactively, or symlink as .git/hooks/commit-msg to check
# your messages before commit.
#
# Examples:
# commitlint foo                          # commit message in ./foo
# commitlint HEAD^!                       # last commit
# commitlint d5d19f841^!                  # this commit
# commitlint -20                          # last 20 commits
# commitlint master..                     # commits in this branch
# commitlint 1.21..1.22 | grep -F [FAIL]  # bad commits in 1.22 release cycle
# commitlint                              # unpushed commits

set -e

if [[ -n ${NO_COLOR+set} ]]
then
    RED=""
    GRN=""
    NRM=""
else
    RED="\033[31m"
    GRN="\033[32m"
    NRM="\033[0m"
fi

# checkcommit GITHASH - check this commit's message and print result
function checkcommit()
{
    HASH=${1}
    MSG=$(git log -1 "$HASH" --pretty=format:"%s%n%b")
    checkmsg "$MSG"
}

# checkmsg MSG [GITHASH] - check this commit message and print result
function checkmsg()
{
    MSG=${1}
    HASH=${2}
    if [[ -n $HASH ]]
    then
        HASH="$HASH "
    fi

    SUMMARY=$(echo "$MSG" | head -1)
    FMT="%s%-60s  %b${NRM}\n"

    # Ignore certain boring commits, like a github merge commit
    # or a temporary "git commit --fixup" commit.
    if echo "$SUMMARY" | grep -qE '^(Merge|fixup!)'
    then 
        # shellcheck disable=SC2059
        printf "$FMT" "$HASH" "$SUMMARY" "[ignored]"

    # Does the summary follow the conventions described in COMMITS.md ? Roughly:
    # [;][feat|imp|fix[!]:] topic: Summary
    #
    # Some passing examples:
    # feat: accounts: --types shows account types (#1820)
    # imp!: journal: Remove deprecated account type code syntax from account directives.
    # fix: types: Ensure auto postings can match against and be matched by type: queries.
    # tools: commitlint: allow a git "fixup! " prefix
    # doc: releasing: tweaks
    #
    # Some failing examples:
    # Revert "imp: ui: accounts: also show declared accounts, even if unused"
    # Update description of hledger-check-tagfiles.hs
    # ; PR-template: Change comment syntax
    # ; cabal.project: Drop compatibility comment
    #
    # At least one type/topic prefix is required. The first must be all word characters,
    # others may contain commas/slashes/spaces.
    # Spaces are allowed around the ; (and before the ! and :, though it's not preferred).
    elif ! echo "$SUMMARY" | grep -qE '^( *[;!] *)?\w+ *!? *:( *\w[\w,/ ]* *:)*'  # keep synced with COMMITS.md:
    then
        # shellcheck disable=SC2059
        printf "$FMT" "$HASH" "$SUMMARY" "${RED}[FAIL]"
        STATUS=1

    # Looks like a good commit.
    else
        # shellcheck disable=SC2059
        printf "$FMT" "$HASH" "$SUMMARY" "${GRN}[ok]"
    fi
}

STATUS=
RANGE=${*:-"@{u}.."}  # @{u} = upstream

if [[ $RANGE =~ (-|\.\.|\^!) ]]
then
    HASHES=$(git log --abbrev-commit --pretty=format:%h "$RANGE")
    for HASH in $HASHES; do checkcommit "$HASH"; done
else
    MSG=$(cat "$1")
    checkmsg "$MSG"
fi

if [[ -z $STATUS ]]
then
    printf "" # "${GRN}Ok${NRM}\n"
else
    # shellcheck disable=SC2059
    printf "\n${RED}Commit(s) not in preferred style.${NRM}\n"
    cat <<EOF
---------------------------------------------------------------------------
Commit messages should follow this format:

  [;]type[!]: [topic:] summary

  [Optional description, ready for release notes/changelog.]

Explanation:

The subject line should have a type: prefix. Common types:
 feat imp fix            - end-user changes (->release notes & changelogs)
 cha pkg lib             - packager/builder/lib-user changes (->changelogs)
 dev doc test ci ref cln - developer changes (->just commit log, mostly)

It can additionally have a topic prefix (and optionally subtopics), such as:
 bin examples install cli ui web print reg bal bs balcmds journal csv ...
 (see https://hledger.org/CONTRIBUTING.html#open-issues -> COMPONENT)
Space, comma and slash are also tolerated inside topics for now. Eg:
 imp: bs, cf, is: cli/doc: blah blah blah

Mention any related issues, usually parenthesised at end of summary: (#1234)
! indicates a breaking change.
; skips expensive CI tests.

These conventions are evolving. In practice, any type or topic will do.
Use your best judgement and we'll polish during code review.
More context: https://hledger.org/CONTRIBUTING.html#commit-messages

You can set up this script to check your commit messages locally:
1. before committing:
   a. safer but must redo:  cp tools/commitlint .git/hooks/commit-msg
   b. more convenient:      ln -s ../../tools/commitlint .git/hooks/commit-msg
2. before pushing:          tools/commitlint && git push
---------------------------------------------------------------------------
EOF

fi

exit 0"$STATUS"
